From d324474467727fa31c76c206acb027acd8925fa1 Mon Sep 17 00:00:00 2001
From: NateN1222 <nathannichols454@gmail.com>
Date: Sun, 3 Sep 2017 17:38:26 -0500
Subject: Implemented a default whitelist

---
 hash_script/node_modules/node-fetch/.npmignore     |   41 +
 hash_script/node_modules/node-fetch/.travis.yml    |   12 +
 hash_script/node_modules/node-fetch/CHANGELOG.md   |  158 +++
 .../node_modules/node-fetch/ERROR-HANDLING.md      |   21 +
 hash_script/node_modules/node-fetch/LICENSE.md     |   22 +
 hash_script/node_modules/node-fetch/LIMITS.md      |   27 +
 hash_script/node_modules/node-fetch/README.md      |  210 +++
 hash_script/node_modules/node-fetch/index.js       |  271 ++++
 hash_script/node_modules/node-fetch/lib/body.js    |  261 ++++
 .../node_modules/node-fetch/lib/fetch-error.js     |   34 +
 hash_script/node_modules/node-fetch/lib/headers.js |  141 ++
 hash_script/node_modules/node-fetch/lib/index.js   | 1416 +++++++++++++++++++
 hash_script/node_modules/node-fetch/lib/request.js |   75 +
 .../node_modules/node-fetch/lib/response.js        |   50 +
 hash_script/node_modules/node-fetch/package.json   |  110 ++
 hash_script/node_modules/node-fetch/test/dummy.txt |    1 +
 hash_script/node_modules/node-fetch/test/server.js |  340 +++++
 hash_script/node_modules/node-fetch/test/test.js   | 1487 ++++++++++++++++++++
 18 files changed, 4677 insertions(+)
 create mode 100644 hash_script/node_modules/node-fetch/.npmignore
 create mode 100644 hash_script/node_modules/node-fetch/.travis.yml
 create mode 100644 hash_script/node_modules/node-fetch/CHANGELOG.md
 create mode 100644 hash_script/node_modules/node-fetch/ERROR-HANDLING.md
 create mode 100644 hash_script/node_modules/node-fetch/LICENSE.md
 create mode 100644 hash_script/node_modules/node-fetch/LIMITS.md
 create mode 100644 hash_script/node_modules/node-fetch/README.md
 create mode 100644 hash_script/node_modules/node-fetch/index.js
 create mode 100644 hash_script/node_modules/node-fetch/lib/body.js
 create mode 100644 hash_script/node_modules/node-fetch/lib/fetch-error.js
 create mode 100644 hash_script/node_modules/node-fetch/lib/headers.js
 create mode 100644 hash_script/node_modules/node-fetch/lib/index.js
 create mode 100644 hash_script/node_modules/node-fetch/lib/request.js
 create mode 100644 hash_script/node_modules/node-fetch/lib/response.js
 create mode 100644 hash_script/node_modules/node-fetch/package.json
 create mode 100644 hash_script/node_modules/node-fetch/test/dummy.txt
 create mode 100644 hash_script/node_modules/node-fetch/test/server.js
 create mode 100644 hash_script/node_modules/node-fetch/test/test.js

(limited to 'hash_script/node_modules/node-fetch')

diff --git a/hash_script/node_modules/node-fetch/.npmignore b/hash_script/node_modules/node-fetch/.npmignore
new file mode 100644
index 0000000..a9cb254
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/.npmignore
@@ -0,0 +1,41 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# Commenting this out is preferred by some people, see
+# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
+node_modules
+
+# Users Environment Variables
+.lock-wscript
+
+# OS files
+.DS_Store
+
+# Coveralls token files
+.coveralls.yml
+
+## ignore some files from 2.x branch
+
+.nyc_output
+lib/index.js
+lib/index.es.js
+package-lock.json
diff --git a/hash_script/node_modules/node-fetch/.travis.yml b/hash_script/node_modules/node-fetch/.travis.yml
new file mode 100644
index 0000000..44b72f0
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/.travis.yml
@@ -0,0 +1,12 @@
+language: node_js
+node_js:
+  - "0.10"
+  - "0.12"
+  - "node"
+env:
+  - FORMDATA_VERSION=1.0.0
+  - FORMDATA_VERSION=2.1.0
+before_script:
+  - 'if [ "$FORMDATA_VERSION" ]; then npm install form-data@^$FORMDATA_VERSION; fi'
+before_install: if [[ `npm -v` < 3 ]]; then npm install -g npm@1.4.28; fi
+script: npm run coverage
\ No newline at end of file
diff --git a/hash_script/node_modules/node-fetch/CHANGELOG.md b/hash_script/node_modules/node-fetch/CHANGELOG.md
new file mode 100644
index 0000000..ea8ebe7
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/CHANGELOG.md
@@ -0,0 +1,158 @@
+
+Changelog
+=========
+
+
+# 1.x release
+
+(Note: `1.x` will only have backported bugfix releases beyond `1.7.0`)
+
+## v1.7.2
+
+- Fix: when using node-fetch with test framework such as `jest`, `instanceof` check could fail in `Headers` class. This is causing some header values, such as `set-cookie`, to be dropped incorrectly.
+
+## v1.7.1
+
+- Fix: close local test server properly under Node 8.
+
+## v1.7.0
+
+- Fix: revert change in `v1.6.2` where 204 no-content response is handled with a special case, this conflicts with browser Fetch implementation (as browsers always throw when res.json() parses an empty string). Since this is an operational error, it's wrapped in a `FetchError` for easier error handling.
+- Fix: move code coverage tool to codecov platform and update travis config
+
+## v1.6.3
+
+- Enhance: error handling document to explain `FetchError` design
+- Fix: support `form-data` 2.x releases (requires `form-data` >= 2.1.0)
+
+## v1.6.2
+
+- Enhance: minor document update
+- Fix: response.json() returns empty object on 204 no-content response instead of throwing a syntax error
+
+## v1.6.1
+
+- Fix: if `res.body` is a non-stream non-formdata object, we will call `body.toString` and send it as a string
+- Fix: `counter` value is incorrectly set to `follow` value when wrapping Request instance
+- Fix: documentation update
+
+## v1.6.0
+
+- Enhance: added `res.buffer()` api for convenience, it returns body as a Node.js buffer
+- Enhance: better old server support by handling raw deflate response
+- Enhance: skip encoding detection for non-HTML/XML response
+- Enhance: minor document update
+- Fix: HEAD request doesn't need decompression, as body is empty
+- Fix: `req.body` now accepts a Node.js buffer
+
+## v1.5.3
+
+- Fix: handle 204 and 304 responses when body is empty but content-encoding is gzip/deflate
+- Fix: allow resolving response and cloned response in any order
+- Fix: avoid setting `content-length` when `form-data` body use streams
+- Fix: send DELETE request with content-length when body is present
+- Fix: allow any url when calling new Request, but still reject non-http(s) url in fetch
+
+## v1.5.2
+
+- Fix: allow node.js core to handle keep-alive connection pool when passing a custom agent
+
+## v1.5.1
+
+- Fix: redirect mode `manual` should work even when there is no redirection or broken redirection
+
+## v1.5.0
+
+- Enhance: rejected promise now use custom `Error` (thx to @pekeler)
+- Enhance: `FetchError` contains `err.type` and `err.code`, allows for better error handling (thx to @pekeler)
+- Enhance: basic support for redirect mode `manual` and `error`, allows for location header extraction (thx to @jimmywarting for the initial PR)
+
+## v1.4.1
+
+- Fix: wrapping Request instance with FormData body again should preserve the body as-is
+
+## v1.4.0
+
+- Enhance: Request and Response now have `clone` method (thx to @kirill-konshin for the initial PR)
+- Enhance: Request and Response now have proper string and buffer body support (thx to @kirill-konshin)
+- Enhance: Body constructor has been refactored out (thx to @kirill-konshin)
+- Enhance: Headers now has `forEach` method (thx to @tricoder42)
+- Enhance: back to 100% code coverage
+- Fix: better form-data support (thx to @item4)
+- Fix: better character encoding detection under chunked encoding (thx to @dsuket for the initial PR)
+
+## v1.3.3
+
+- Fix: make sure `Content-Length` header is set when body is string for POST/PUT/PATCH requests
+- Fix: handle body stream error, for cases such as incorrect `Content-Encoding` header
+- Fix: when following certain redirects, use `GET` on subsequent request per Fetch Spec
+- Fix: `Request` and `Response` constructors now parse headers input using `Headers`
+
+## v1.3.2
+
+- Enhance: allow auto detect of form-data input (no `FormData` spec on node.js, this is form-data specific feature)
+
+## v1.3.1
+
+- Enhance: allow custom host header to be set (server-side only feature, as it's a forbidden header on client-side)
+
+## v1.3.0
+
+- Enhance: now `fetch.Request` is exposed as well
+
+## v1.2.1
+
+- Enhance: `Headers` now normalized `Number` value to `String`, prevent common mistakes
+
+## v1.2.0
+
+- Enhance: now fetch.Headers and fetch.Response are exposed, making testing easier
+
+## v1.1.2
+
+- Fix: `Headers` should only support `String` and `Array` properties, and ignore others
+
+## v1.1.1
+
+- Enhance: now req.headers accept both plain object and `Headers` instance
+
+## v1.1.0
+
+- Enhance: timeout now also applies to response body (in case of slow response)
+- Fix: timeout is now cleared properly when fetch is done/has failed
+
+## v1.0.6
+
+- Fix: less greedy content-type charset matching
+
+## v1.0.5
+
+- Fix: when `follow = 0`, fetch should not follow redirect
+- Enhance: update tests for better coverage
+- Enhance: code formatting
+- Enhance: clean up doc
+
+## v1.0.4
+
+- Enhance: test iojs support
+- Enhance: timeout attached to socket event only fire once per redirect
+
+## v1.0.3
+
+- Fix: response size limit should reject large chunk
+- Enhance: added character encoding detection for xml, such as rss/atom feed (encoding in DTD)
+
+## v1.0.2
+
+- Fix: added res.ok per spec change
+
+## v1.0.0
+
+- Enhance: better test coverage and doc
+
+
+# 0.x release
+
+## v0.1
+
+- Major: initial public release
diff --git a/hash_script/node_modules/node-fetch/ERROR-HANDLING.md b/hash_script/node_modules/node-fetch/ERROR-HANDLING.md
new file mode 100644
index 0000000..0e4025d
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/ERROR-HANDLING.md
@@ -0,0 +1,21 @@
+
+Error handling with node-fetch
+==============================
+
+Because `window.fetch` isn't designed to transparent about the cause of request errors, we have to come up with our own solutions.
+
+The basics:
+
+- All [operational errors](https://www.joyent.com/node-js/production/design/errors) are rejected as [FetchError](https://github.com/bitinn/node-fetch/blob/master/lib/fetch-error.js), you can handle them all through promise `catch` clause.
+
+- All errors comes with `err.message` detailing the cause of errors.
+
+- All errors originated from `node-fetch` are marked with custom `err.type`.
+
+- All errors originated from Node.js core are marked with `err.type = system`, and contains addition `err.code` and `err.errno` for error handling, they are alias to error codes thrown by Node.js core.
+
+- [Programmer errors](https://www.joyent.com/node-js/production/design/errors) are either thrown as soon as possible, or rejected with default `Error` with `err.message` for ease of troubleshooting.
+
+List of error types:
+
+- Because we maintain 100% coverage, see [test.js](https://github.com/bitinn/node-fetch/blob/master/test/test.js) for a full list of custom `FetchError` types, as well as some of the common errors from Node.js
diff --git a/hash_script/node_modules/node-fetch/LICENSE.md b/hash_script/node_modules/node-fetch/LICENSE.md
new file mode 100644
index 0000000..660ffec
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/LICENSE.md
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 David Frank
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/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/hash_script/node_modules/node-fetch/LIMITS.md b/hash_script/node_modules/node-fetch/LIMITS.md
new file mode 100644
index 0000000..d0d41fc
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/LIMITS.md
@@ -0,0 +1,27 @@
+
+Known differences
+=================
+
+*As of 1.x release*
+
+- Topics such as Cross-Origin, Content Security Policy, Mixed Content, Service Workers are ignored, given our server-side context.
+
+- URL input must be an absolute URL, using either `http` or `https` as scheme.
+
+- On the upside, there are no forbidden headers, and `res.url` contains the final url when following redirects.
+
+- For convenience, `res.body` is a transform stream, so decoding can be handled independently.
+
+- Similarly, `req.body` can either be a string, a buffer or a readable stream.
+
+- Also, you can handle rejected fetch requests through checking `err.type` and `err.code`.
+
+- Only support `res.text()`, `res.json()`, `res.buffer()` at the moment, until there are good use-cases for blob/arrayBuffer.
+
+- There is currently no built-in caching, as server-side caching varies by use-cases.
+
+- Current implementation lacks server-side cookie store, you will need to extract `Set-Cookie` headers manually.
+
+- If you are using `res.clone()` and writing an isomorphic app, note that stream on Node.js have a smaller internal buffer size (16Kb, aka `highWaterMark`) from client-side browsers (>1Mb, not consistent across browsers).
+
+- ES6 features such as `headers.entries()` are missing at the moment, but you can use `headers.raw()` to retrieve the raw headers object.
diff --git a/hash_script/node_modules/node-fetch/README.md b/hash_script/node_modules/node-fetch/README.md
new file mode 100644
index 0000000..0bfb387
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/README.md
@@ -0,0 +1,210 @@
+
+node-fetch
+==========
+
+[![npm version][npm-image]][npm-url]
+[![build status][travis-image]][travis-url]
+[![coverage status][codecov-image]][codecov-url]
+
+A light-weight module that brings `window.fetch` to Node.js
+
+
+# Motivation
+
+Instead of implementing `XMLHttpRequest` in Node.js to run browser-specific [Fetch polyfill](https://github.com/github/fetch), why not go from native `http` to `Fetch` API directly? Hence `node-fetch`, minimal code for a `window.fetch` compatible API on Node.js runtime.
+
+See Matt Andrews' [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) for isomorphic usage (exports `node-fetch` for server-side, `whatwg-fetch` for client-side).
+
+
+# Features
+
+- Stay consistent with `window.fetch` API.
+- Make conscious trade-off when following [whatwg fetch spec](https://fetch.spec.whatwg.org/) and [stream spec](https://streams.spec.whatwg.org/) implementation details, document known difference.
+- Use native promise, but allow substituting it with [insert your favorite promise library].
+- Use native stream for body, on both request and response.
+- Decode content encoding (gzip/deflate) properly, and convert string output (such as `res.text()` and `res.json()`) to UTF-8 automatically.
+- Useful extensions such as timeout, redirect limit, response size limit, [explicit errors](https://github.com/bitinn/node-fetch/blob/master/ERROR-HANDLING.md) for troubleshooting.
+
+
+# Difference from client-side fetch
+
+- See [Known Differences](https://github.com/bitinn/node-fetch/blob/master/LIMITS.md) for details.
+- If you happen to use a missing feature that `window.fetch` offers, feel free to open an issue.
+- Pull requests are welcomed too!
+
+
+# Install
+
+`npm install node-fetch --save`
+
+
+# Usage
+
+```javascript
+var fetch = require('node-fetch');
+
+// if you are on node v0.10, set a Promise library first, eg.
+// fetch.Promise = require('bluebird');
+
+// plain text or html
+
+fetch('https://github.com/')
+	.then(function(res) {
+		return res.text();
+	}).then(function(body) {
+		console.log(body);
+	});
+
+// json
+
+fetch('https://api.github.com/users/github')
+	.then(function(res) {
+		return res.json();
+	}).then(function(json) {
+		console.log(json);
+	});
+
+// catching network error
+// 3xx-5xx responses are NOT network errors, and should be handled in then()
+// you only need one catch() at the end of your promise chain
+
+fetch('http://domain.invalid/')
+	.catch(function(err) {
+		console.log(err);
+	});
+
+// stream
+// the node.js way is to use stream when possible
+
+fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png')
+	.then(function(res) {
+		var dest = fs.createWriteStream('./octocat.png');
+		res.body.pipe(dest);
+	});
+
+// buffer
+// if you prefer to cache binary data in full, use buffer()
+// note that buffer() is a node-fetch only API
+
+var fileType = require('file-type');
+fetch('https://assets-cdn.github.com/images/modules/logos_page/Octocat.png')
+	.then(function(res) {
+		return res.buffer();
+	}).then(function(buffer) {
+		fileType(buffer);
+	});
+
+// meta
+
+fetch('https://github.com/')
+	.then(function(res) {
+		console.log(res.ok);
+		console.log(res.status);
+		console.log(res.statusText);
+		console.log(res.headers.raw());
+		console.log(res.headers.get('content-type'));
+	});
+
+// post
+
+fetch('http://httpbin.org/post', { method: 'POST', body: 'a=1' })
+	.then(function(res) {
+		return res.json();
+	}).then(function(json) {
+		console.log(json);
+	});
+
+// post with stream from resumer
+
+var resumer = require('resumer');
+var stream = resumer().queue('a=1').end();
+fetch('http://httpbin.org/post', { method: 'POST', body: stream })
+	.then(function(res) {
+		return res.json();
+	}).then(function(json) {
+		console.log(json);
+	});
+
+// post with form-data (detect multipart)
+
+var FormData = require('form-data');
+var form = new FormData();
+form.append('a', 1);
+fetch('http://httpbin.org/post', { method: 'POST', body: form })
+	.then(function(res) {
+		return res.json();
+	}).then(function(json) {
+		console.log(json);
+	});
+
+// post with form-data (custom headers)
+// note that getHeaders() is non-standard API
+
+var FormData = require('form-data');
+var form = new FormData();
+form.append('a', 1);
+fetch('http://httpbin.org/post', { method: 'POST', body: form, headers: form.getHeaders() })
+	.then(function(res) {
+		return res.json();
+	}).then(function(json) {
+		console.log(json);
+	});
+
+// node 0.12+, yield with co
+
+var co = require('co');
+co(function *() {
+	var res = yield fetch('https://api.github.com/users/github');
+	var json = yield res.json();
+	console.log(res);
+});
+```
+
+See [test cases](https://github.com/bitinn/node-fetch/blob/master/test/test.js) for more examples.
+
+
+# API
+
+## fetch(url, options)
+
+Returns a `Promise`
+
+### Url
+
+Should be an absolute url, eg `http://example.com/`
+
+### Options
+
+default values are shown, note that only `method`, `headers`, `redirect` and `body` are allowed in `window.fetch`, others are node.js extensions.
+
+```
+{
+	method: 'GET'
+	, headers: {}        // request header. format {a:'1'} or {b:['1','2','3']}
+	, redirect: 'follow' // set to `manual` to extract redirect headers, `error` to reject redirect
+	, follow: 20         // maximum redirect count. 0 to not follow redirect
+	, timeout: 0         // req/res timeout in ms, it resets on redirect. 0 to disable (OS limit applies)
+	, compress: true     // support gzip/deflate content encoding. false to disable
+	, size: 0            // maximum response body size in bytes. 0 to disable
+	, body: empty        // request body. can be a string, buffer, readable stream
+	, agent: null        // http.Agent instance, allows custom proxy, certificate etc.
+}
+```
+
+
+# License
+
+MIT
+
+
+# Acknowledgement
+
+Thanks to [github/fetch](https://github.com/github/fetch) for providing a solid implementation reference.
+
+
+[npm-image]: https://img.shields.io/npm/v/node-fetch.svg?style=flat-square
+[npm-url]: https://www.npmjs.com/package/node-fetch
+[travis-image]: https://img.shields.io/travis/bitinn/node-fetch.svg?style=flat-square
+[travis-url]: https://travis-ci.org/bitinn/node-fetch
+[codecov-image]: https://img.shields.io/codecov/c/github/bitinn/node-fetch.svg?style=flat-square
+[codecov-url]: https://codecov.io/gh/bitinn/node-fetch
diff --git a/hash_script/node_modules/node-fetch/index.js b/hash_script/node_modules/node-fetch/index.js
new file mode 100644
index 0000000..8f6730d
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/index.js
@@ -0,0 +1,271 @@
+
+/**
+ * index.js
+ *
+ * a request API compatible with window.fetch
+ */
+
+var parse_url = require('url').parse;
+var resolve_url = require('url').resolve;
+var http = require('http');
+var https = require('https');
+var zlib = require('zlib');
+var stream = require('stream');
+
+var Body = require('./lib/body');
+var Response = require('./lib/response');
+var Headers = require('./lib/headers');
+var Request = require('./lib/request');
+var FetchError = require('./lib/fetch-error');
+
+// commonjs
+module.exports = Fetch;
+// es6 default export compatibility
+module.exports.default = module.exports;
+
+/**
+ * Fetch class
+ *
+ * @param   Mixed    url   Absolute url or Request instance
+ * @param   Object   opts  Fetch options
+ * @return  Promise
+ */
+function Fetch(url, opts) {
+
+	// allow call as function
+	if (!(this instanceof Fetch))
+		return new Fetch(url, opts);
+
+	// allow custom promise
+	if (!Fetch.Promise) {
+		throw new Error('native promise missing, set Fetch.Promise to your favorite alternative');
+	}
+
+	Body.Promise = Fetch.Promise;
+
+	var self = this;
+
+	// wrap http.request into fetch
+	return new Fetch.Promise(function(resolve, reject) {
+		// build request object
+		var options = new Request(url, opts);
+
+		if (!options.protocol || !options.hostname) {
+			throw new Error('only absolute urls are supported');
+		}
+
+		if (options.protocol !== 'http:' && options.protocol !== 'https:') {
+			throw new Error('only http(s) protocols are supported');
+		}
+
+		var send;
+		if (options.protocol === 'https:') {
+			send = https.request;
+		} else {
+			send = http.request;
+		}
+
+		// normalize headers
+		var headers = new Headers(options.headers);
+
+		if (options.compress) {
+			headers.set('accept-encoding', 'gzip,deflate');
+		}
+
+		if (!headers.has('user-agent')) {
+			headers.set('user-agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
+		}
+
+		if (!headers.has('connection') && !options.agent) {
+			headers.set('connection', 'close');
+		}
+
+		if (!headers.has('accept')) {
+			headers.set('accept', '*/*');
+		}
+
+		// detect form data input from form-data module, this hack avoid the need to pass multipart header manually
+		if (!headers.has('content-type') && options.body && typeof options.body.getBoundary === 'function') {
+			headers.set('content-type', 'multipart/form-data; boundary=' + options.body.getBoundary());
+		}
+
+		// bring node-fetch closer to browser behavior by setting content-length automatically
+		if (!headers.has('content-length') && /post|put|patch|delete/i.test(options.method)) {
+			if (typeof options.body === 'string') {
+				headers.set('content-length', Buffer.byteLength(options.body));
+			// detect form data input from form-data module, this hack avoid the need to add content-length header manually
+			} else if (options.body && typeof options.body.getLengthSync === 'function') {
+				// for form-data 1.x
+				if (options.body._lengthRetrievers && options.body._lengthRetrievers.length == 0) {
+					headers.set('content-length', options.body.getLengthSync().toString());
+				// for form-data 2.x
+				} else if (options.body.hasKnownLength && options.body.hasKnownLength()) {
+					headers.set('content-length', options.body.getLengthSync().toString());
+				}
+			// this is only necessary for older nodejs releases (before iojs merge)
+			} else if (options.body === undefined || options.body === null) {
+				headers.set('content-length', '0');
+			}
+		}
+
+		options.headers = headers.raw();
+
+		// http.request only support string as host header, this hack make custom host header possible
+		if (options.headers.host) {
+			options.headers.host = options.headers.host[0];
+		}
+
+		// send request
+		var req = send(options);
+		var reqTimeout;
+
+		if (options.timeout) {
+			req.once('socket', function(socket) {
+				reqTimeout = setTimeout(function() {
+					req.abort();
+					reject(new FetchError('network timeout at: ' + options.url, 'request-timeout'));
+				}, options.timeout);
+			});
+		}
+
+		req.on('error', function(err) {
+			clearTimeout(reqTimeout);
+			reject(new FetchError('request to ' + options.url + ' failed, reason: ' + err.message, 'system', err));
+		});
+
+		req.on('response', function(res) {
+			clearTimeout(reqTimeout);
+
+			// handle redirect
+			if (self.isRedirect(res.statusCode) && options.redirect !== 'manual') {
+				if (options.redirect === 'error') {
+					reject(new FetchError('redirect mode is set to error: ' + options.url, 'no-redirect'));
+					return;
+				}
+
+				if (options.counter >= options.follow) {
+					reject(new FetchError('maximum redirect reached at: ' + options.url, 'max-redirect'));
+					return;
+				}
+
+				if (!res.headers.location) {
+					reject(new FetchError('redirect location header missing at: ' + options.url, 'invalid-redirect'));
+					return;
+				}
+
+				// per fetch spec, for POST request with 301/302 response, or any request with 303 response, use GET when following redirect
+				if (res.statusCode === 303
+					|| ((res.statusCode === 301 || res.statusCode === 302) && options.method === 'POST'))
+				{
+					options.method = 'GET';
+					delete options.body;
+					delete options.headers['content-length'];
+				}
+
+				options.counter++;
+
+				resolve(Fetch(resolve_url(options.url, res.headers.location), options));
+				return;
+			}
+
+			// normalize location header for manual redirect mode
+			var headers = new Headers(res.headers);
+			if (options.redirect === 'manual' && headers.has('location')) {
+				headers.set('location', resolve_url(options.url, headers.get('location')));
+			}
+
+			// prepare response
+			var body = res.pipe(new stream.PassThrough());
+			var response_options = {
+				url: options.url
+				, status: res.statusCode
+				, statusText: res.statusMessage
+				, headers: headers
+				, size: options.size
+				, timeout: options.timeout
+			};
+
+			// response object
+			var output;
+
+			// in following scenarios we ignore compression support
+			// 1. compression support is disabled
+			// 2. HEAD request
+			// 3. no content-encoding header
+			// 4. no content response (204)
+			// 5. content not modified response (304)
+			if (!options.compress || options.method === 'HEAD' || !headers.has('content-encoding') || res.statusCode === 204 || res.statusCode === 304) {
+				output = new Response(body, response_options);
+				resolve(output);
+				return;
+			}
+
+			// otherwise, check for gzip or deflate
+			var name = headers.get('content-encoding');
+
+			// for gzip
+			if (name == 'gzip' || name == 'x-gzip') {
+				body = body.pipe(zlib.createGunzip());
+				output = new Response(body, response_options);
+				resolve(output);
+				return;
+
+			// for deflate
+			} else if (name == 'deflate' || name == 'x-deflate') {
+				// handle the infamous raw deflate response from old servers
+				// a hack for old IIS and Apache servers
+				var raw = res.pipe(new stream.PassThrough());
+				raw.once('data', function(chunk) {
+					// see http://stackoverflow.com/questions/37519828
+					if ((chunk[0] & 0x0F) === 0x08) {
+						body = body.pipe(zlib.createInflate());
+					} else {
+						body = body.pipe(zlib.createInflateRaw());
+					}
+					output = new Response(body, response_options);
+					resolve(output);
+				});
+				return;
+			}
+
+			// otherwise, use response as-is
+			output = new Response(body, response_options);
+			resolve(output);
+			return;
+		});
+
+		// accept string, buffer or readable stream as body
+		// per spec we will call tostring on non-stream objects
+		if (typeof options.body === 'string') {
+			req.write(options.body);
+			req.end();
+		} else if (options.body instanceof Buffer) {
+			req.write(options.body);
+			req.end();
+		} else if (typeof options.body === 'object' && options.body.pipe) {
+			options.body.pipe(req);
+		} else if (typeof options.body === 'object') {
+			req.write(options.body.toString());
+			req.end();
+		} else {
+			req.end();
+		}
+	});
+
+};
+
+/**
+ * Redirect code matching
+ *
+ * @param   Number   code  Status code
+ * @return  Boolean
+ */
+Fetch.prototype.isRedirect = function(code) {
+	return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
+}
+
+// expose Promise
+Fetch.Promise = global.Promise;
+Fetch.Response = Response;
+Fetch.Headers = Headers;
+Fetch.Request = Request;
diff --git a/hash_script/node_modules/node-fetch/lib/body.js b/hash_script/node_modules/node-fetch/lib/body.js
new file mode 100644
index 0000000..19bc003
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/lib/body.js
@@ -0,0 +1,261 @@
+
+/**
+ * body.js
+ *
+ * Body interface provides common methods for Request and Response
+ */
+
+var convert = require('encoding').convert;
+var bodyStream = require('is-stream');
+var PassThrough = require('stream').PassThrough;
+var FetchError = require('./fetch-error');
+
+module.exports = Body;
+
+/**
+ * Body class
+ *
+ * @param   Stream  body  Readable stream
+ * @param   Object  opts  Response options
+ * @return  Void
+ */
+function Body(body, opts) {
+
+	opts = opts || {};
+
+	this.body = body;
+	this.bodyUsed = false;
+	this.size = opts.size || 0;
+	this.timeout = opts.timeout || 0;
+	this._raw = [];
+	this._abort = false;
+
+}
+
+/**
+ * Decode response as json
+ *
+ * @return  Promise
+ */
+Body.prototype.json = function() {
+
+	var self = this;
+
+	return this._decode().then(function(buffer) {
+		try {
+			return JSON.parse(buffer.toString());
+		} catch (err) {
+			return Body.Promise.reject(new FetchError('invalid json response body at ' + self.url + ' reason: ' + err.message, 'invalid-json'));
+		}
+	});
+
+};
+
+/**
+ * Decode response as text
+ *
+ * @return  Promise
+ */
+Body.prototype.text = function() {
+
+	return this._decode().then(function(buffer) {
+		return buffer.toString();
+	});
+
+};
+
+/**
+ * Decode response as buffer (non-spec api)
+ *
+ * @return  Promise
+ */
+Body.prototype.buffer = function() {
+
+	return this._decode();
+
+};
+
+/**
+ * Decode buffers into utf-8 string
+ *
+ * @return  Promise
+ */
+Body.prototype._decode = function() {
+
+	var self = this;
+
+	if (this.bodyUsed) {
+		return Body.Promise.reject(new Error('body used already for: ' + this.url));
+	}
+
+	this.bodyUsed = true;
+	this._bytes = 0;
+	this._abort = false;
+	this._raw = [];
+
+	return new Body.Promise(function(resolve, reject) {
+		var resTimeout;
+
+		// body is string
+		if (typeof self.body === 'string') {
+			self._bytes = self.body.length;
+			self._raw = [new Buffer(self.body)];
+			return resolve(self._convert());
+		}
+
+		// body is buffer
+		if (self.body instanceof Buffer) {
+			self._bytes = self.body.length;
+			self._raw = [self.body];
+			return resolve(self._convert());
+		}
+
+		// allow timeout on slow response body
+		if (self.timeout) {
+			resTimeout = setTimeout(function() {
+				self._abort = true;
+				reject(new FetchError('response timeout at ' + self.url + ' over limit: ' + self.timeout, 'body-timeout'));
+			}, self.timeout);
+		}
+
+		// handle stream error, such as incorrect content-encoding
+		self.body.on('error', function(err) {
+			reject(new FetchError('invalid response body at: ' + self.url + ' reason: ' + err.message, 'system', err));
+		});
+
+		// body is stream
+		self.body.on('data', function(chunk) {
+			if (self._abort || chunk === null) {
+				return;
+			}
+
+			if (self.size && self._bytes + chunk.length > self.size) {
+				self._abort = true;
+				reject(new FetchError('content size at ' + self.url + ' over limit: ' + self.size, 'max-size'));
+				return;
+			}
+
+			self._bytes += chunk.length;
+			self._raw.push(chunk);
+		});
+
+		self.body.on('end', function() {
+			if (self._abort) {
+				return;
+			}
+
+			clearTimeout(resTimeout);
+			resolve(self._convert());
+		});
+	});
+
+};
+
+/**
+ * Detect buffer encoding and convert to target encoding
+ * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
+ *
+ * @param   String  encoding  Target encoding
+ * @return  String
+ */
+Body.prototype._convert = function(encoding) {
+
+	encoding = encoding || 'utf-8';
+
+	var ct = this.headers.get('content-type');
+	var charset = 'utf-8';
+	var res, str;
+
+	// header
+	if (ct) {
+		// skip encoding detection altogether if not html/xml/plain text
+		if (!/text\/html|text\/plain|\+xml|\/xml/i.test(ct)) {
+			return Buffer.concat(this._raw);
+		}
+
+		res = /charset=([^;]*)/i.exec(ct);
+	}
+
+	// no charset in content type, peek at response body for at most 1024 bytes
+	if (!res && this._raw.length > 0) {
+		for (var i = 0; i < this._raw.length; i++) {
+			str += this._raw[i].toString()
+			if (str.length > 1024) {
+				break;
+			}
+		}
+		str = str.substr(0, 1024);
+	}
+
+	// html5
+	if (!res && str) {
+		res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
+	}
+
+	// html4
+	if (!res && str) {
+		res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);
+
+		if (res) {
+			res = /charset=(.*)/i.exec(res.pop());
+		}
+	}
+
+	// xml
+	if (!res && str) {
+		res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
+	}
+
+	// found charset
+	if (res) {
+		charset = res.pop();
+
+		// prevent decode issues when sites use incorrect encoding
+		// ref: https://hsivonen.fi/encoding-menu/
+		if (charset === 'gb2312' || charset === 'gbk') {
+			charset = 'gb18030';
+		}
+	}
+
+	// turn raw buffers into a single utf-8 buffer
+	return convert(
+		Buffer.concat(this._raw)
+		, encoding
+		, charset
+	);
+
+};
+
+/**
+ * Clone body given Res/Req instance
+ *
+ * @param   Mixed  instance  Response or Request instance
+ * @return  Mixed
+ */
+Body.prototype._clone = function(instance) {
+	var p1, p2;
+	var body = instance.body;
+
+	// don't allow cloning a used body
+	if (instance.bodyUsed) {
+		throw new Error('cannot clone body after it is used');
+	}
+
+	// check that body is a stream and not form-data object
+	// note: we can't clone the form-data object without having it as a dependency
+	if (bodyStream(body) && typeof body.getBoundary !== 'function') {
+		// tee instance body
+		p1 = new PassThrough();
+		p2 = new PassThrough();
+		body.pipe(p1);
+		body.pipe(p2);
+		// set instance body to teed body and return the other teed body
+		instance.body = p1;
+		body = p2;
+	}
+
+	return body;
+}
+
+// expose Promise
+Body.Promise = global.Promise;
diff --git a/hash_script/node_modules/node-fetch/lib/fetch-error.js b/hash_script/node_modules/node-fetch/lib/fetch-error.js
new file mode 100644
index 0000000..7cabfb3
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/lib/fetch-error.js
@@ -0,0 +1,34 @@
+
+/**
+ * fetch-error.js
+ *
+ * FetchError interface for operational errors
+ */
+
+module.exports = FetchError;
+
+/**
+ * Create FetchError instance
+ *
+ * @param   String      message      Error message for human
+ * @param   String      type         Error type for machine
+ * @param   String      systemError  For Node.js system error
+ * @return  FetchError
+ */
+function FetchError(message, type, systemError) {
+
+	// hide custom error implementation details from end-users
+	Error.captureStackTrace(this, this.constructor);
+
+	this.name = this.constructor.name;
+	this.message = message;
+	this.type = type;
+
+	// when err.type is `system`, err.code contains system error code
+	if (systemError) {
+		this.code = this.errno = systemError.code;
+	}
+
+}
+
+require('util').inherits(FetchError, Error);
diff --git a/hash_script/node_modules/node-fetch/lib/headers.js b/hash_script/node_modules/node-fetch/lib/headers.js
new file mode 100644
index 0000000..af20749
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/lib/headers.js
@@ -0,0 +1,141 @@
+
+/**
+ * headers.js
+ *
+ * Headers class offers convenient helpers
+ */
+
+module.exports = Headers;
+
+/**
+ * Headers class
+ *
+ * @param   Object  headers  Response headers
+ * @return  Void
+ */
+function Headers(headers) {
+
+	var self = this;
+	this._headers = {};
+
+	// Headers
+	if (headers instanceof Headers) {
+		headers = headers.raw();
+	}
+
+	// plain object
+	for (var prop in headers) {
+		if (!headers.hasOwnProperty(prop)) {
+			continue;
+		}
+
+		if (typeof headers[prop] === 'string') {
+			this.set(prop, headers[prop]);
+
+		} else if (typeof headers[prop] === 'number' && !isNaN(headers[prop])) {
+			this.set(prop, headers[prop].toString());
+
+		} else if (Array.isArray(headers[prop])) {
+			headers[prop].forEach(function(item) {
+				self.append(prop, item.toString());
+			});
+		}
+	}
+
+}
+
+/**
+ * Return first header value given name
+ *
+ * @param   String  name  Header name
+ * @return  Mixed
+ */
+Headers.prototype.get = function(name) {
+	var list = this._headers[name.toLowerCase()];
+	return list ? list[0] : null;
+};
+
+/**
+ * Return all header values given name
+ *
+ * @param   String  name  Header name
+ * @return  Array
+ */
+Headers.prototype.getAll = function(name) {
+	if (!this.has(name)) {
+		return [];
+	}
+
+	return this._headers[name.toLowerCase()];
+};
+
+/**
+ * Iterate over all headers
+ *
+ * @param   Function  callback  Executed for each item with parameters (value, name, thisArg)
+ * @param   Boolean   thisArg   `this` context for callback function
+ * @return  Void
+ */
+Headers.prototype.forEach = function(callback, thisArg) {
+	Object.getOwnPropertyNames(this._headers).forEach(function(name) {
+		this._headers[name].forEach(function(value) {
+			callback.call(thisArg, value, name, this)
+		}, this)
+	}, this)
+}
+
+/**
+ * Overwrite header values given name
+ *
+ * @param   String  name   Header name
+ * @param   String  value  Header value
+ * @return  Void
+ */
+Headers.prototype.set = function(name, value) {
+	this._headers[name.toLowerCase()] = [value];
+};
+
+/**
+ * Append a value onto existing header
+ *
+ * @param   String  name   Header name
+ * @param   String  value  Header value
+ * @return  Void
+ */
+Headers.prototype.append = function(name, value) {
+	if (!this.has(name)) {
+		this.set(name, value);
+		return;
+	}
+
+	this._headers[name.toLowerCase()].push(value);
+};
+
+/**
+ * Check for header name existence
+ *
+ * @param   String   name  Header name
+ * @return  Boolean
+ */
+Headers.prototype.has = function(name) {
+	return this._headers.hasOwnProperty(name.toLowerCase());
+};
+
+/**
+ * Delete all header values given name
+ *
+ * @param   String  name  Header name
+ * @return  Void
+ */
+Headers.prototype['delete'] = function(name) {
+	delete this._headers[name.toLowerCase()];
+};
+
+/**
+ * Return raw headers (non-spec api)
+ *
+ * @return  Object
+ */
+Headers.prototype.raw = function() {
+	return this._headers;
+};
diff --git a/hash_script/node_modules/node-fetch/lib/index.js b/hash_script/node_modules/node-fetch/lib/index.js
new file mode 100644
index 0000000..f100854
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/lib/index.js
@@ -0,0 +1,1416 @@
+'use strict';
+
+Object.defineProperty(exports, '__esModule', { value: true });
+
+// Based on https://github.com/tmpvar/jsdom/blob/aa85b2abf07766ff7bf5c1f6daafb3726f2f2db5/lib/jsdom/living/blob.js
+// (MIT licensed)
+
+const BUFFER = Symbol('buffer');
+const TYPE = Symbol('type');
+const CLOSED = Symbol('closed');
+
+class Blob {
+	constructor() {
+		Object.defineProperty(this, Symbol.toStringTag, {
+			value: 'Blob',
+			writable: false,
+			enumerable: false,
+			configurable: true
+		});
+
+		this[CLOSED] = false;
+		this[TYPE] = '';
+
+		const blobParts = arguments[0];
+		const options = arguments[1];
+
+		const buffers = [];
+
+		if (blobParts) {
+			const a = blobParts;
+			const length = Number(a.length);
+			for (let i = 0; i < length; i++) {
+				const element = a[i];
+				let buffer;
+				if (element instanceof Buffer) {
+					buffer = element;
+				} else if (ArrayBuffer.isView(element)) {
+					buffer = Buffer.from(element.buffer, element.byteOffset, element.byteLength);
+				} else if (element instanceof ArrayBuffer) {
+					buffer = Buffer.from(element);
+				} else if (element instanceof Blob) {
+					buffer = element[BUFFER];
+				} else {
+					buffer = Buffer.from(typeof element === 'string' ? element : String(element));
+				}
+				buffers.push(buffer);
+			}
+		}
+
+		this[BUFFER] = Buffer.concat(buffers);
+
+		let type = options && options.type !== undefined && String(options.type).toLowerCase();
+		if (type && !/[^\u0020-\u007E]/.test(type)) {
+			this[TYPE] = type;
+		}
+	}
+	get size() {
+		return this[CLOSED] ? 0 : this[BUFFER].length;
+	}
+	get type() {
+		return this[TYPE];
+	}
+	get isClosed() {
+		return this[CLOSED];
+	}
+	slice() {
+		const size = this.size;
+
+		const start = arguments[0];
+		const end = arguments[1];
+		let relativeStart, relativeEnd;
+		if (start === undefined) {
+			relativeStart = 0;
+		} else if (start < 0) {
+			relativeStart = Math.max(size + start, 0);
+		} else {
+			relativeStart = Math.min(start, size);
+		}
+		if (end === undefined) {
+			relativeEnd = size;
+		} else if (end < 0) {
+			relativeEnd = Math.max(size + end, 0);
+		} else {
+			relativeEnd = Math.min(end, size);
+		}
+		const span = Math.max(relativeEnd - relativeStart, 0);
+
+		const buffer = this[BUFFER];
+		const slicedBuffer = buffer.slice(relativeStart, relativeStart + span);
+		const blob = new Blob([], { type: arguments[2] });
+		blob[BUFFER] = slicedBuffer;
+		blob[CLOSED] = this[CLOSED];
+		return blob;
+	}
+	close() {
+		this[CLOSED] = true;
+	}
+}
+
+Object.defineProperty(Blob.prototype, Symbol.toStringTag, {
+	value: 'BlobPrototype',
+	writable: false,
+	enumerable: false,
+	configurable: true
+});
+
+/**
+ * fetch-error.js
+ *
+ * FetchError interface for operational errors
+ */
+
+/**
+ * Create FetchError instance
+ *
+ * @param   String      message      Error message for human
+ * @param   String      type         Error type for machine
+ * @param   String      systemError  For Node.js system error
+ * @return  FetchError
+ */
+function FetchError(message, type, systemError) {
+  Error.call(this, message);
+
+  this.message = message;
+  this.type = type;
+
+  // when err.type is `system`, err.code contains system error code
+  if (systemError) {
+    this.code = this.errno = systemError.code;
+  }
+
+  // hide custom error implementation details from end-users
+  Error.captureStackTrace(this, this.constructor);
+}
+
+FetchError.prototype = Object.create(Error.prototype);
+FetchError.prototype.constructor = FetchError;
+FetchError.prototype.name = 'FetchError';
+
+/**
+ * body.js
+ *
+ * Body interface provides common methods for Request and Response
+ */
+
+const Stream = require('stream');
+
+var _require$1 = require('stream');
+
+const PassThrough$1 = _require$1.PassThrough;
+
+
+const DISTURBED = Symbol('disturbed');
+
+let convert;
+try {
+	convert = require('encoding').convert;
+} catch (e) {}
+
+/**
+ * Body class
+ *
+ * Cannot use ES6 class because Body must be called with .call().
+ *
+ * @param   Stream  body  Readable stream
+ * @param   Object  opts  Response options
+ * @return  Void
+ */
+function Body(body) {
+	var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
+	    _ref$size = _ref.size;
+
+	let size = _ref$size === undefined ? 0 : _ref$size;
+	var _ref$timeout = _ref.timeout;
+	let timeout = _ref$timeout === undefined ? 0 : _ref$timeout;
+
+	if (body == null) {
+		// body is undefined or null
+		body = null;
+	} else if (typeof body === 'string') {
+		// body is string
+	} else if (isURLSearchParams(body)) {
+		// body is a URLSearchParams
+	} else if (body instanceof Blob) {
+		// body is blob
+	} else if (Buffer.isBuffer(body)) {
+		// body is buffer
+	} else if (body instanceof Stream) {
+		// body is stream
+	} else {
+		// none of the above
+		// coerce to string
+		body = String(body);
+	}
+	this.body = body;
+	this[DISTURBED] = false;
+	this.size = size;
+	this.timeout = timeout;
+}
+
+Body.prototype = {
+	get bodyUsed() {
+		return this[DISTURBED];
+	},
+
+	/**
+  * Decode response as ArrayBuffer
+  *
+  * @return  Promise
+  */
+	arrayBuffer() {
+		return consumeBody.call(this).then(function (buf) {
+			return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
+		});
+	},
+
+	/**
+  * Return raw response as Blob
+  *
+  * @return Promise
+  */
+	blob() {
+		let ct = this.headers && this.headers.get('content-type') || '';
+		return consumeBody.call(this).then(function (buf) {
+			return Object.assign(
+			// Prevent copying
+			new Blob([], {
+				type: ct.toLowerCase()
+			}), {
+				[BUFFER]: buf
+			});
+		});
+	},
+
+	/**
+  * Decode response as json
+  *
+  * @return  Promise
+  */
+	json() {
+		var _this = this;
+
+		return consumeBody.call(this).then(function (buffer) {
+			try {
+				return JSON.parse(buffer.toString());
+			} catch (err) {
+				return Body.Promise.reject(new FetchError(`invalid json response body at ${_this.url} reason: ${err.message}`, 'invalid-json'));
+			}
+		});
+	},
+
+	/**
+  * Decode response as text
+  *
+  * @return  Promise
+  */
+	text() {
+		return consumeBody.call(this).then(function (buffer) {
+			return buffer.toString();
+		});
+	},
+
+	/**
+  * Decode response as buffer (non-spec api)
+  *
+  * @return  Promise
+  */
+	buffer() {
+		return consumeBody.call(this);
+	},
+
+	/**
+  * Decode response as text, while automatically detecting the encoding and
+  * trying to decode to UTF-8 (non-spec api)
+  *
+  * @return  Promise
+  */
+	textConverted() {
+		var _this2 = this;
+
+		return consumeBody.call(this).then(function (buffer) {
+			return convertBody(buffer, _this2.headers);
+		});
+	}
+
+};
+
+Body.mixIn = function (proto) {
+	for (const name of Object.getOwnPropertyNames(Body.prototype)) {
+		// istanbul ignore else: future proof
+		if (!(name in proto)) {
+			const desc = Object.getOwnPropertyDescriptor(Body.prototype, name);
+			Object.defineProperty(proto, name, desc);
+		}
+	}
+};
+
+/**
+ * Decode buffers into utf-8 string
+ *
+ * @return  Promise
+ */
+function consumeBody(body) {
+	var _this3 = this;
+
+	if (this[DISTURBED]) {
+		return Body.Promise.reject(new Error(`body used already for: ${this.url}`));
+	}
+
+	this[DISTURBED] = true;
+
+	// body is null
+	if (this.body === null) {
+		return Body.Promise.resolve(Buffer.alloc(0));
+	}
+
+	// body is string
+	if (typeof this.body === 'string') {
+		return Body.Promise.resolve(Buffer.from(this.body));
+	}
+
+	// body is blob
+	if (this.body instanceof Blob) {
+		return Body.Promise.resolve(this.body[BUFFER]);
+	}
+
+	// body is buffer
+	if (Buffer.isBuffer(this.body)) {
+		return Body.Promise.resolve(this.body);
+	}
+
+	// istanbul ignore if: should never happen
+	if (!(this.body instanceof Stream)) {
+		return Body.Promise.resolve(Buffer.alloc(0));
+	}
+
+	// body is stream
+	// get ready to actually consume the body
+	let accum = [];
+	let accumBytes = 0;
+	let abort = false;
+
+	return new Body.Promise(function (resolve, reject) {
+		let resTimeout;
+
+		// allow timeout on slow response body
+		if (_this3.timeout) {
+			resTimeout = setTimeout(function () {
+				abort = true;
+				reject(new FetchError(`Response timeout while trying to fetch ${_this3.url} (over ${_this3.timeout}ms)`, 'body-timeout'));
+			}, _this3.timeout);
+		}
+
+		// handle stream error, such as incorrect content-encoding
+		_this3.body.on('error', function (err) {
+			reject(new FetchError(`Invalid response body while trying to fetch ${_this3.url}: ${err.message}`, 'system', err));
+		});
+
+		_this3.body.on('data', function (chunk) {
+			if (abort || chunk === null) {
+				return;
+			}
+
+			if (_this3.size && accumBytes + chunk.length > _this3.size) {
+				abort = true;
+				reject(new FetchError(`content size at ${_this3.url} over limit: ${_this3.size}`, 'max-size'));
+				return;
+			}
+
+			accumBytes += chunk.length;
+			accum.push(chunk);
+		});
+
+		_this3.body.on('end', function () {
+			if (abort) {
+				return;
+			}
+
+			clearTimeout(resTimeout);
+			resolve(Buffer.concat(accum));
+		});
+	});
+}
+
+/**
+ * Detect buffer encoding and convert to target encoding
+ * ref: http://www.w3.org/TR/2011/WD-html5-20110113/parsing.html#determining-the-character-encoding
+ *
+ * @param   Buffer  buffer    Incoming buffer
+ * @param   String  encoding  Target encoding
+ * @return  String
+ */
+function convertBody(buffer, headers) {
+	if (typeof convert !== 'function') {
+		throw new Error('The package `encoding` must be installed to use the textConverted() function');
+	}
+
+	const ct = headers.get('content-type');
+	let charset = 'utf-8';
+	let res, str;
+
+	// header
+	if (ct) {
+		res = /charset=([^;]*)/i.exec(ct);
+	}
+
+	// no charset in content type, peek at response body for at most 1024 bytes
+	str = buffer.slice(0, 1024).toString();
+
+	// html5
+	if (!res && str) {
+		res = /<meta.+?charset=(['"])(.+?)\1/i.exec(str);
+	}
+
+	// html4
+	if (!res && str) {
+		res = /<meta[\s]+?http-equiv=(['"])content-type\1[\s]+?content=(['"])(.+?)\2/i.exec(str);
+
+		if (res) {
+			res = /charset=(.*)/i.exec(res.pop());
+		}
+	}
+
+	// xml
+	if (!res && str) {
+		res = /<\?xml.+?encoding=(['"])(.+?)\1/i.exec(str);
+	}
+
+	// found charset
+	if (res) {
+		charset = res.pop();
+
+		// prevent decode issues when sites use incorrect encoding
+		// ref: https://hsivonen.fi/encoding-menu/
+		if (charset === 'gb2312' || charset === 'gbk') {
+			charset = 'gb18030';
+		}
+	}
+
+	// turn raw buffers into a single utf-8 buffer
+	return convert(buffer, 'UTF-8', charset).toString();
+}
+
+/**
+ * Detect a URLSearchParams object
+ * ref: https://github.com/bitinn/node-fetch/issues/296#issuecomment-307598143
+ *
+ * @param   Object  obj     Object to detect by type or brand
+ * @return  String
+ */
+function isURLSearchParams(obj) {
+	// Duck-typing as a necessary condition.
+	if (typeof obj !== 'object' || typeof obj.append !== 'function' || typeof obj.delete !== 'function' || typeof obj.get !== 'function' || typeof obj.getAll !== 'function' || typeof obj.has !== 'function' || typeof obj.set !== 'function') {
+		return false;
+	}
+
+	// Brand-checking and more duck-typing as optional condition.
+	return obj.constructor.name === 'URLSearchParams' || Object.prototype.toString.call(obj) === '[object URLSearchParams]' || typeof obj.sort === 'function';
+}
+
+/**
+ * Clone body given Res/Req instance
+ *
+ * @param   Mixed  instance  Response or Request instance
+ * @return  Mixed
+ */
+function clone(instance) {
+	let p1, p2;
+	let body = instance.body;
+
+	// don't allow cloning a used body
+	if (instance.bodyUsed) {
+		throw new Error('cannot clone body after it is used');
+	}
+
+	// check that body is a stream and not form-data object
+	// note: we can't clone the form-data object without having it as a dependency
+	if (body instanceof Stream && typeof body.getBoundary !== 'function') {
+		// tee instance body
+		p1 = new PassThrough$1();
+		p2 = new PassThrough$1();
+		body.pipe(p1);
+		body.pipe(p2);
+		// set instance body to teed body and return the other teed body
+		instance.body = p1;
+		body = p2;
+	}
+
+	return body;
+}
+
+/**
+ * Performs the operation "extract a `Content-Type` value from |object|" as
+ * specified in the specification:
+ * https://fetch.spec.whatwg.org/#concept-bodyinit-extract
+ *
+ * This function assumes that instance.body is present and non-null.
+ *
+ * @param   Mixed  instance  Response or Request instance
+ */
+function extractContentType(instance) {
+	const body = instance.body;
+
+	// istanbul ignore if: Currently, because of a guard in Request, body
+	// can never be null. Included here for completeness.
+
+	if (body === null) {
+		// body is null
+		return null;
+	} else if (typeof body === 'string') {
+		// body is string
+		return 'text/plain;charset=UTF-8';
+	} else if (isURLSearchParams(body)) {
+		// body is a URLSearchParams
+		return 'application/x-www-form-urlencoded;charset=UTF-8';
+	} else if (body instanceof Blob) {
+		// body is blob
+		return body.type || null;
+	} else if (Buffer.isBuffer(body)) {
+		// body is buffer
+		return null;
+	} else if (typeof body.getBoundary === 'function') {
+		// detect form data input from form-data module
+		return `multipart/form-data;boundary=${body.getBoundary()}`;
+	} else {
+		// body is stream
+		// can't really do much about this
+		return null;
+	}
+}
+
+function getTotalBytes(instance) {
+	const body = instance.body;
+
+	// istanbul ignore if: included for completion
+
+	if (body === null) {
+		// body is null
+		return 0;
+	} else if (typeof body === 'string') {
+		// body is string
+		return Buffer.byteLength(body);
+	} else if (isURLSearchParams(body)) {
+		// body is URLSearchParams
+		return Buffer.byteLength(String(body));
+	} else if (body instanceof Blob) {
+		// body is blob
+		return body.size;
+	} else if (Buffer.isBuffer(body)) {
+		// body is buffer
+		return body.length;
+	} else if (body && typeof body.getLengthSync === 'function') {
+		// detect form data input from form-data module
+		if (body._lengthRetrievers && body._lengthRetrievers.length == 0 || // 1.x
+		body.hasKnownLength && body.hasKnownLength()) {
+			// 2.x
+			return body.getLengthSync();
+		}
+		return null;
+	} else {
+		// body is stream
+		// can't really do much about this
+		return null;
+	}
+}
+
+function writeToStream(dest, instance) {
+	const body = instance.body;
+
+
+	if (body === null) {
+		// body is null
+		dest.end();
+	} else if (typeof body === 'string') {
+		// body is string
+		dest.write(body);
+		dest.end();
+	} else if (isURLSearchParams(body)) {
+		// body is URLSearchParams
+		dest.write(Buffer.from(String(body)));
+		dest.end();
+	} else if (body instanceof Blob) {
+		// body is blob
+		dest.write(body[BUFFER]);
+		dest.end();
+	} else if (Buffer.isBuffer(body)) {
+		// body is buffer
+		dest.write(body);
+		dest.end();
+	} else {
+		// body is stream
+		body.pipe(dest);
+	}
+}
+
+// expose Promise
+Body.Promise = global.Promise;
+
+/**
+ * A set of utilities borrowed from Node.js' _http_common.js
+ */
+
+/**
+ * Verifies that the given val is a valid HTTP token
+ * per the rules defined in RFC 7230
+ * See https://tools.ietf.org/html/rfc7230#section-3.2.6
+ *
+ * Allowed characters in an HTTP token:
+ * ^_`a-z  94-122
+ * A-Z     65-90
+ * -       45
+ * 0-9     48-57
+ * !       33
+ * #$%&'   35-39
+ * *+      42-43
+ * .       46
+ * |       124
+ * ~       126
+ *
+ * This implementation of checkIsHttpToken() loops over the string instead of
+ * using a regular expression since the former is up to 180% faster with v8 4.9
+ * depending on the string length (the shorter the string, the larger the
+ * performance difference)
+ *
+ * Additionally, checkIsHttpToken() is currently designed to be inlinable by v8,
+ * so take care when making changes to the implementation so that the source
+ * code size does not exceed v8's default max_inlined_source_size setting.
+ **/
+/* istanbul ignore next */
+function isValidTokenChar(ch) {
+  if (ch >= 94 && ch <= 122) return true;
+  if (ch >= 65 && ch <= 90) return true;
+  if (ch === 45) return true;
+  if (ch >= 48 && ch <= 57) return true;
+  if (ch === 34 || ch === 40 || ch === 41 || ch === 44) return false;
+  if (ch >= 33 && ch <= 46) return true;
+  if (ch === 124 || ch === 126) return true;
+  return false;
+}
+/* istanbul ignore next */
+function checkIsHttpToken(val) {
+  if (typeof val !== 'string' || val.length === 0) return false;
+  if (!isValidTokenChar(val.charCodeAt(0))) return false;
+  const len = val.length;
+  if (len > 1) {
+    if (!isValidTokenChar(val.charCodeAt(1))) return false;
+    if (len > 2) {
+      if (!isValidTokenChar(val.charCodeAt(2))) return false;
+      if (len > 3) {
+        if (!isValidTokenChar(val.charCodeAt(3))) return false;
+        for (var i = 4; i < len; i++) {
+          if (!isValidTokenChar(val.charCodeAt(i))) return false;
+        }
+      }
+    }
+  }
+  return true;
+}
+/**
+ * True if val contains an invalid field-vchar
+ *  field-value    = *( field-content / obs-fold )
+ *  field-content  = field-vchar [ 1*( SP / HTAB ) field-vchar ]
+ *  field-vchar    = VCHAR / obs-text
+ *
+ * checkInvalidHeaderChar() is currently designed to be inlinable by v8,
+ * so take care when making changes to the implementation so that the source
+ * code size does not exceed v8's default max_inlined_source_size setting.
+ **/
+/* istanbul ignore next */
+function checkInvalidHeaderChar(val) {
+  val += '';
+  if (val.length < 1) return false;
+  var c = val.charCodeAt(0);
+  if (c <= 31 && c !== 9 || c > 255 || c === 127) return true;
+  if (val.length < 2) return false;
+  c = val.charCodeAt(1);
+  if (c <= 31 && c !== 9 || c > 255 || c === 127) return true;
+  if (val.length < 3) return false;
+  c = val.charCodeAt(2);
+  if (c <= 31 && c !== 9 || c > 255 || c === 127) return true;
+  for (var i = 3; i < val.length; ++i) {
+    c = val.charCodeAt(i);
+    if (c <= 31 && c !== 9 || c > 255 || c === 127) return true;
+  }
+  return false;
+}
+
+/**
+ * headers.js
+ *
+ * Headers class offers convenient helpers
+ */
+
+function sanitizeName(name) {
+	name += '';
+	if (!checkIsHttpToken(name)) {
+		throw new TypeError(`${name} is not a legal HTTP header name`);
+	}
+	return name.toLowerCase();
+}
+
+function sanitizeValue(value) {
+	value += '';
+	if (checkInvalidHeaderChar(value)) {
+		throw new TypeError(`${value} is not a legal HTTP header value`);
+	}
+	return value;
+}
+
+const MAP = Symbol('map');
+class Headers {
+	/**
+  * Headers class
+  *
+  * @param   Object  headers  Response headers
+  * @return  Void
+  */
+	constructor() {
+		let init = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined;
+
+		this[MAP] = Object.create(null);
+
+		if (init instanceof Headers) {
+			const rawHeaders = init.raw();
+			const headerNames = Object.keys(rawHeaders);
+
+			for (const headerName of headerNames) {
+				for (const value of rawHeaders[headerName]) {
+					this.append(headerName, value);
+				}
+			}
+
+			return;
+		}
+
+		// We don't worry about converting prop to ByteString here as append()
+		// will handle it.
+		if (init == null) {
+			// no op
+		} else if (typeof init === 'object') {
+			const method = init[Symbol.iterator];
+			if (method != null) {
+				if (typeof method !== 'function') {
+					throw new TypeError('Header pairs must be iterable');
+				}
+
+				// sequence<sequence<ByteString>>
+				// Note: per spec we have to first exhaust the lists then process them
+				const pairs = [];
+				for (const pair of init) {
+					if (typeof pair !== 'object' || typeof pair[Symbol.iterator] !== 'function') {
+						throw new TypeError('Each header pair must be iterable');
+					}
+					pairs.push(Array.from(pair));
+				}
+
+				for (const pair of pairs) {
+					if (pair.length !== 2) {
+						throw new TypeError('Each header pair must be a name/value tuple');
+					}
+					this.append(pair[0], pair[1]);
+				}
+			} else {
+				// record<ByteString, ByteString>
+				for (const key of Object.keys(init)) {
+					const value = init[key];
+					this.append(key, value);
+				}
+			}
+		} else {
+			throw new TypeError('Provided initializer must be an object');
+		}
+
+		Object.defineProperty(this, Symbol.toStringTag, {
+			value: 'Headers',
+			writable: false,
+			enumerable: false,
+			configurable: true
+		});
+	}
+
+	/**
+  * Return first header value given name
+  *
+  * @param   String  name  Header name
+  * @return  Mixed
+  */
+	get(name) {
+		const list = this[MAP][sanitizeName(name)];
+		if (!list) {
+			return null;
+		}
+
+		return list.join(', ');
+	}
+
+	/**
+  * Iterate over all headers
+  *
+  * @param   Function  callback  Executed for each item with parameters (value, name, thisArg)
+  * @param   Boolean   thisArg   `this` context for callback function
+  * @return  Void
+  */
+	forEach(callback) {
+		let thisArg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
+
+		let pairs = getHeaderPairs(this);
+		let i = 0;
+		while (i < pairs.length) {
+			var _pairs$i = pairs[i];
+			const name = _pairs$i[0],
+			      value = _pairs$i[1];
+
+			callback.call(thisArg, value, name, this);
+			pairs = getHeaderPairs(this);
+			i++;
+		}
+	}
+
+	/**
+  * Overwrite header values given name
+  *
+  * @param   String  name   Header name
+  * @param   String  value  Header value
+  * @return  Void
+  */
+	set(name, value) {
+		this[MAP][sanitizeName(name)] = [sanitizeValue(value)];
+	}
+
+	/**
+  * Append a value onto existing header
+  *
+  * @param   String  name   Header name
+  * @param   String  value  Header value
+  * @return  Void
+  */
+	append(name, value) {
+		if (!this.has(name)) {
+			this.set(name, value);
+			return;
+		}
+
+		this[MAP][sanitizeName(name)].push(sanitizeValue(value));
+	}
+
+	/**
+  * Check for header name existence
+  *
+  * @param   String   name  Header name
+  * @return  Boolean
+  */
+	has(name) {
+		return !!this[MAP][sanitizeName(name)];
+	}
+
+	/**
+  * Delete all header values given name
+  *
+  * @param   String  name  Header name
+  * @return  Void
+  */
+	delete(name) {
+		delete this[MAP][sanitizeName(name)];
+	}
+
+	/**
+  * Return raw headers (non-spec api)
+  *
+  * @return  Object
+  */
+	raw() {
+		return this[MAP];
+	}
+
+	/**
+  * Get an iterator on keys.
+  *
+  * @return  Iterator
+  */
+	keys() {
+		return createHeadersIterator(this, 'key');
+	}
+
+	/**
+  * Get an iterator on values.
+  *
+  * @return  Iterator
+  */
+	values() {
+		return createHeadersIterator(this, 'value');
+	}
+
+	/**
+  * Get an iterator on entries.
+  *
+  * This is the default iterator of the Headers object.
+  *
+  * @return  Iterator
+  */
+	[Symbol.iterator]() {
+		return createHeadersIterator(this, 'key+value');
+	}
+}
+Headers.prototype.entries = Headers.prototype[Symbol.iterator];
+
+Object.defineProperty(Headers.prototype, Symbol.toStringTag, {
+	value: 'HeadersPrototype',
+	writable: false,
+	enumerable: false,
+	configurable: true
+});
+
+function getHeaderPairs(headers, kind) {
+	const keys = Object.keys(headers[MAP]).sort();
+	return keys.map(kind === 'key' ? function (k) {
+		return [k];
+	} : function (k) {
+		return [k, headers.get(k)];
+	});
+}
+
+const INTERNAL = Symbol('internal');
+
+function createHeadersIterator(target, kind) {
+	const iterator = Object.create(HeadersIteratorPrototype);
+	iterator[INTERNAL] = {
+		target,
+		kind,
+		index: 0
+	};
+	return iterator;
+}
+
+const HeadersIteratorPrototype = Object.setPrototypeOf({
+	next() {
+		// istanbul ignore if
+		if (!this || Object.getPrototypeOf(this) !== HeadersIteratorPrototype) {
+			throw new TypeError('Value of `this` is not a HeadersIterator');
+		}
+
+		var _INTERNAL = this[INTERNAL];
+		const target = _INTERNAL.target,
+		      kind = _INTERNAL.kind,
+		      index = _INTERNAL.index;
+
+		const values = getHeaderPairs(target, kind);
+		const len = values.length;
+		if (index >= len) {
+			return {
+				value: undefined,
+				done: true
+			};
+		}
+
+		const pair = values[index];
+		this[INTERNAL].index = index + 1;
+
+		let result;
+		if (kind === 'key') {
+			result = pair[0];
+		} else if (kind === 'value') {
+			result = pair[1];
+		} else {
+			result = pair;
+		}
+
+		return {
+			value: result,
+			done: false
+		};
+	}
+}, Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())));
+
+Object.defineProperty(HeadersIteratorPrototype, Symbol.toStringTag, {
+	value: 'HeadersIterator',
+	writable: false,
+	enumerable: false,
+	configurable: true
+});
+
+/**
+ * response.js
+ *
+ * Response class provides content decoding
+ */
+
+var _require$2 = require('http');
+
+const STATUS_CODES = _require$2.STATUS_CODES;
+
+/**
+ * Response class
+ *
+ * @param   Stream  body  Readable stream
+ * @param   Object  opts  Response options
+ * @return  Void
+ */
+
+class Response {
+	constructor() {
+		let body = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+		let opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+		Body.call(this, body, opts);
+
+		this.url = opts.url;
+		this.status = opts.status || 200;
+		this.statusText = opts.statusText || STATUS_CODES[this.status];
+
+		this.headers = new Headers(opts.headers);
+
+		Object.defineProperty(this, Symbol.toStringTag, {
+			value: 'Response',
+			writable: false,
+			enumerable: false,
+			configurable: true
+		});
+	}
+
+	/**
+  * Convenience property representing if the request ended normally
+  */
+	get ok() {
+		return this.status >= 200 && this.status < 300;
+	}
+
+	/**
+  * Clone this response
+  *
+  * @return  Response
+  */
+	clone() {
+
+		return new Response(clone(this), {
+			url: this.url,
+			status: this.status,
+			statusText: this.statusText,
+			headers: this.headers,
+			ok: this.ok
+		});
+	}
+}
+
+Body.mixIn(Response.prototype);
+
+Object.defineProperty(Response.prototype, Symbol.toStringTag, {
+	value: 'ResponsePrototype',
+	writable: false,
+	enumerable: false,
+	configurable: true
+});
+
+/**
+ * request.js
+ *
+ * Request class contains server only options
+ */
+
+var _require$3 = require('url');
+
+const format_url = _require$3.format;
+const parse_url = _require$3.parse;
+
+
+const PARSED_URL = Symbol('url');
+
+/**
+ * Request class
+ *
+ * @param   Mixed   input  Url or Request instance
+ * @param   Object  init   Custom options
+ * @return  Void
+ */
+class Request {
+	constructor(input) {
+		let init = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+
+		let parsedURL;
+
+		// normalize input
+		if (!(input instanceof Request)) {
+			if (input && input.href) {
+				// in order to support Node.js' Url objects; though WHATWG's URL objects
+				// will fall into this branch also (since their `toString()` will return
+				// `href` property anyway)
+				parsedURL = parse_url(input.href);
+			} else {
+				// coerce input to a string before attempting to parse
+				parsedURL = parse_url(`${input}`);
+			}
+			input = {};
+		} else {
+			parsedURL = parse_url(input.url);
+		}
+
+		let method = init.method || input.method || 'GET';
+
+		if ((init.body != null || input instanceof Request && input.body !== null) && (method === 'GET' || method === 'HEAD')) {
+			throw new TypeError('Request with GET/HEAD method cannot have body');
+		}
+
+		let inputBody = init.body != null ? init.body : input instanceof Request && input.body !== null ? clone(input) : null;
+
+		Body.call(this, inputBody, {
+			timeout: init.timeout || input.timeout || 0,
+			size: init.size || input.size || 0
+		});
+
+		// fetch spec options
+		this.method = method.toUpperCase();
+		this.redirect = init.redirect || input.redirect || 'follow';
+		this.headers = new Headers(init.headers || input.headers || {});
+
+		if (init.body != null) {
+			const contentType = extractContentType(this);
+			if (contentType !== null && !this.headers.has('Content-Type')) {
+				this.headers.append('Content-Type', contentType);
+			}
+		}
+
+		// server only options
+		this.follow = init.follow !== undefined ? init.follow : input.follow !== undefined ? input.follow : 20;
+		this.compress = init.compress !== undefined ? init.compress : input.compress !== undefined ? input.compress : true;
+		this.counter = init.counter || input.counter || 0;
+		this.agent = init.agent || input.agent;
+
+		this[PARSED_URL] = parsedURL;
+		Object.defineProperty(this, Symbol.toStringTag, {
+			value: 'Request',
+			writable: false,
+			enumerable: false,
+			configurable: true
+		});
+	}
+
+	get url() {
+		return format_url(this[PARSED_URL]);
+	}
+
+	/**
+  * Clone this request
+  *
+  * @return  Request
+  */
+	clone() {
+		return new Request(this);
+	}
+}
+
+Body.mixIn(Request.prototype);
+
+Object.defineProperty(Request.prototype, Symbol.toStringTag, {
+	value: 'RequestPrototype',
+	writable: false,
+	enumerable: false,
+	configurable: true
+});
+
+function getNodeRequestOptions(request) {
+	const parsedURL = request[PARSED_URL];
+	const headers = new Headers(request.headers);
+
+	// fetch step 3
+	if (!headers.has('Accept')) {
+		headers.set('Accept', '*/*');
+	}
+
+	// Basic fetch
+	if (!parsedURL.protocol || !parsedURL.hostname) {
+		throw new TypeError('Only absolute URLs are supported');
+	}
+
+	if (!/^https?:$/.test(parsedURL.protocol)) {
+		throw new TypeError('Only HTTP(S) protocols are supported');
+	}
+
+	// HTTP-network-or-cache fetch steps 5-9
+	let contentLengthValue = null;
+	if (request.body == null && /^(POST|PUT)$/i.test(request.method)) {
+		contentLengthValue = '0';
+	}
+	if (request.body != null) {
+		const totalBytes = getTotalBytes(request);
+		if (typeof totalBytes === 'number') {
+			contentLengthValue = String(totalBytes);
+		}
+	}
+	if (contentLengthValue) {
+		headers.set('Content-Length', contentLengthValue);
+	}
+
+	// HTTP-network-or-cache fetch step 12
+	if (!headers.has('User-Agent')) {
+		headers.set('User-Agent', 'node-fetch/1.0 (+https://github.com/bitinn/node-fetch)');
+	}
+
+	// HTTP-network-or-cache fetch step 16
+	if (request.compress) {
+		headers.set('Accept-Encoding', 'gzip,deflate');
+	}
+	if (!headers.has('Connection') && !request.agent) {
+		headers.set('Connection', 'close');
+	}
+
+	// HTTP-network fetch step 4
+	// chunked encoding is handled by Node.js
+
+	return Object.assign({}, parsedURL, {
+		method: request.method,
+		headers: headers.raw(),
+		agent: request.agent
+	});
+}
+
+/**
+ * index.js
+ *
+ * a request API compatible with window.fetch
+ */
+
+const http = require('http');
+const https = require('https');
+
+var _require = require('stream');
+
+const PassThrough = _require.PassThrough;
+
+var _require2 = require('url');
+
+const resolve_url = _require2.resolve;
+
+const zlib = require('zlib');
+
+/**
+ * Fetch function
+ *
+ * @param   Mixed    url   Absolute url or Request instance
+ * @param   Object   opts  Fetch options
+ * @return  Promise
+ */
+function fetch(url, opts) {
+
+	// allow custom promise
+	if (!fetch.Promise) {
+		throw new Error('native promise missing, set fetch.Promise to your favorite alternative');
+	}
+
+	Body.Promise = fetch.Promise;
+
+	// wrap http.request into fetch
+	return new fetch.Promise(function (resolve, reject) {
+		// build request object
+		const request = new Request(url, opts);
+		const options = getNodeRequestOptions(request);
+
+		const send = (options.protocol === 'https:' ? https : http).request;
+
+		// http.request only support string as host header, this hack make custom host header possible
+		if (options.headers.host) {
+			options.headers.host = options.headers.host[0];
+		}
+
+		// send request
+		const req = send(options);
+		let reqTimeout;
+
+		if (request.timeout) {
+			req.once('socket', function (socket) {
+				reqTimeout = setTimeout(function () {
+					req.abort();
+					reject(new FetchError(`network timeout at: ${request.url}`, 'request-timeout'));
+				}, request.timeout);
+			});
+		}
+
+		req.on('error', function (err) {
+			clearTimeout(reqTimeout);
+			reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
+		});
+
+		req.on('response', function (res) {
+			clearTimeout(reqTimeout);
+
+			// handle redirect
+			if (fetch.isRedirect(res.statusCode) && request.redirect !== 'manual') {
+				if (request.redirect === 'error') {
+					reject(new FetchError(`redirect mode is set to error: ${request.url}`, 'no-redirect'));
+					return;
+				}
+
+				if (request.counter >= request.follow) {
+					reject(new FetchError(`maximum redirect reached at: ${request.url}`, 'max-redirect'));
+					return;
+				}
+
+				if (!res.headers.location) {
+					reject(new FetchError(`redirect location header missing at: ${request.url}`, 'invalid-redirect'));
+					return;
+				}
+
+				// per fetch spec, for POST request with 301/302 response, or any request with 303 response, use GET when following redirect
+				if (res.statusCode === 303 || (res.statusCode === 301 || res.statusCode === 302) && request.method === 'POST') {
+					request.method = 'GET';
+					request.body = null;
+					request.headers.delete('content-length');
+				}
+
+				request.counter++;
+
+				resolve(fetch(resolve_url(request.url, res.headers.location), request));
+				return;
+			}
+
+			// normalize location header for manual redirect mode
+			const headers = new Headers();
+			for (const name of Object.keys(res.headers)) {
+				if (Array.isArray(res.headers[name])) {
+					for (const val of res.headers[name]) {
+						headers.append(name, val);
+					}
+				} else {
+					headers.append(name, res.headers[name]);
+				}
+			}
+			if (request.redirect === 'manual' && headers.has('location')) {
+				headers.set('location', resolve_url(request.url, headers.get('location')));
+			}
+
+			// prepare response
+			let body = res.pipe(new PassThrough());
+			const response_options = {
+				url: request.url,
+				status: res.statusCode,
+				statusText: res.statusMessage,
+				headers: headers,
+				size: request.size,
+				timeout: request.timeout
+			};
+
+			// HTTP-network fetch step 16.1.2
+			const codings = headers.get('Content-Encoding');
+
+			// HTTP-network fetch step 16.1.3: handle content codings
+
+			// in following scenarios we ignore compression support
+			// 1. compression support is disabled
+			// 2. HEAD request
+			// 3. no Content-Encoding header
+			// 4. no content response (204)
+			// 5. content not modified response (304)
+			if (!request.compress || request.method === 'HEAD' || codings === null || res.statusCode === 204 || res.statusCode === 304) {
+				resolve(new Response(body, response_options));
+				return;
+			}
+
+			// For Node v6+
+			// Be less strict when decoding compressed responses, since sometimes
+			// servers send slightly invalid responses that are still accepted
+			// by common browsers.
+			// Always using Z_SYNC_FLUSH is what cURL does.
+			const zlibOptions = {
+				flush: zlib.Z_SYNC_FLUSH,
+				finishFlush: zlib.Z_SYNC_FLUSH
+			};
+
+			// for gzip
+			if (codings == 'gzip' || codings == 'x-gzip') {
+				body = body.pipe(zlib.createGunzip(zlibOptions));
+				resolve(new Response(body, response_options));
+				return;
+			}
+
+			// for deflate
+			if (codings == 'deflate' || codings == 'x-deflate') {
+				// handle the infamous raw deflate response from old servers
+				// a hack for old IIS and Apache servers
+				const raw = res.pipe(new PassThrough());
+				raw.once('data', function (chunk) {
+					// see http://stackoverflow.com/questions/37519828
+					if ((chunk[0] & 0x0F) === 0x08) {
+						body = body.pipe(zlib.createInflate());
+					} else {
+						body = body.pipe(zlib.createInflateRaw());
+					}
+					resolve(new Response(body, response_options));
+				});
+				return;
+			}
+
+			// otherwise, use response as-is
+			resolve(new Response(body, response_options));
+		});
+
+		writeToStream(req, request);
+	});
+}
+
+/**
+ * Redirect code matching
+ *
+ * @param   Number   code  Status code
+ * @return  Boolean
+ */
+fetch.isRedirect = function (code) {
+	return code === 301 || code === 302 || code === 303 || code === 307 || code === 308;
+};
+
+// expose Promise
+fetch.Promise = global.Promise;
+
+module.exports = exports = fetch;
+exports.Headers = Headers;
+exports.Request = Request;
+exports.Response = Response;
+exports.FetchError = FetchError;
diff --git a/hash_script/node_modules/node-fetch/lib/request.js b/hash_script/node_modules/node-fetch/lib/request.js
new file mode 100644
index 0000000..1a29c29
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/lib/request.js
@@ -0,0 +1,75 @@
+
+/**
+ * request.js
+ *
+ * Request class contains server only options
+ */
+
+var parse_url = require('url').parse;
+var Headers = require('./headers');
+var Body = require('./body');
+
+module.exports = Request;
+
+/**
+ * Request class
+ *
+ * @param   Mixed   input  Url or Request instance
+ * @param   Object  init   Custom options
+ * @return  Void
+ */
+function Request(input, init) {
+	var url, url_parsed;
+
+	// normalize input
+	if (!(input instanceof Request)) {
+		url = input;
+		url_parsed = parse_url(url);
+		input = {};
+	} else {
+		url = input.url;
+		url_parsed = parse_url(url);
+	}
+
+	// normalize init
+	init = init || {};
+
+	// fetch spec options
+	this.method = init.method || input.method || 'GET';
+	this.redirect = init.redirect || input.redirect || 'follow';
+	this.headers = new Headers(init.headers || input.headers || {});
+	this.url = url;
+
+	// server only options
+	this.follow = init.follow !== undefined ?
+		init.follow : input.follow !== undefined ?
+		input.follow : 20;
+	this.compress = init.compress !== undefined ?
+		init.compress : input.compress !== undefined ?
+		input.compress : true;
+	this.counter = init.counter || input.counter || 0;
+	this.agent = init.agent || input.agent;
+
+	Body.call(this, init.body || this._clone(input), {
+		timeout: init.timeout || input.timeout || 0,
+		size: init.size || input.size || 0
+	});
+
+	// server request options
+	this.protocol = url_parsed.protocol;
+	this.hostname = url_parsed.hostname;
+	this.port = url_parsed.port;
+	this.path = url_parsed.path;
+	this.auth = url_parsed.auth;
+}
+
+Request.prototype = Object.create(Body.prototype);
+
+/**
+ * Clone this request
+ *
+ * @return  Request
+ */
+Request.prototype.clone = function() {
+	return new Request(this);
+};
diff --git a/hash_script/node_modules/node-fetch/lib/response.js b/hash_script/node_modules/node-fetch/lib/response.js
new file mode 100644
index 0000000..f96aa85
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/lib/response.js
@@ -0,0 +1,50 @@
+
+/**
+ * response.js
+ *
+ * Response class provides content decoding
+ */
+
+var http = require('http');
+var Headers = require('./headers');
+var Body = require('./body');
+
+module.exports = Response;
+
+/**
+ * Response class
+ *
+ * @param   Stream  body  Readable stream
+ * @param   Object  opts  Response options
+ * @return  Void
+ */
+function Response(body, opts) {
+
+	opts = opts || {};
+
+	this.url = opts.url;
+	this.status = opts.status || 200;
+	this.statusText = opts.statusText || http.STATUS_CODES[this.status];
+	this.headers = new Headers(opts.headers);
+	this.ok = this.status >= 200 && this.status < 300;
+
+	Body.call(this, body, opts);
+
+}
+
+Response.prototype = Object.create(Body.prototype);
+
+/**
+ * Clone this response
+ *
+ * @return  Response
+ */
+Response.prototype.clone = function() {
+	return new Response(this._clone(this), {
+		url: this.url
+		, status: this.status
+		, statusText: this.statusText
+		, headers: this.headers
+		, ok: this.ok
+	});
+};
diff --git a/hash_script/node_modules/node-fetch/package.json b/hash_script/node_modules/node-fetch/package.json
new file mode 100644
index 0000000..dcb8f7b
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/package.json
@@ -0,0 +1,110 @@
+{
+  "_args": [
+    [
+      {
+        "raw": "node-fetch@^1.7.2",
+        "scope": null,
+        "escapedName": "node-fetch",
+        "name": "node-fetch",
+        "rawSpec": "^1.7.2",
+        "spec": ">=1.7.2 <2.0.0",
+        "type": "range"
+      },
+      "/home/nate/Desktop/tar_JDkx6AzcE"
+    ]
+  ],
+  "_from": "node-fetch@>=1.7.2 <2.0.0",
+  "_id": "node-fetch@1.7.2",
+  "_inCache": true,
+  "_location": "/node-fetch",
+  "_nodeVersion": "8.2.1",
+  "_npmOperationalInternal": {
+    "host": "s3://npm-registry-packages",
+    "tmp": "tmp/node-fetch-1.7.2.tgz_1502191381219_0.664517188211903"
+  },
+  "_npmUser": {
+    "name": "bitinn",
+    "email": "bitinn@gmail.com"
+  },
+  "_npmVersion": "5.3.0",
+  "_phantomChildren": {},
+  "_requested": {
+    "raw": "node-fetch@^1.7.2",
+    "scope": null,
+    "escapedName": "node-fetch",
+    "name": "node-fetch",
+    "rawSpec": "^1.7.2",
+    "spec": ">=1.7.2 <2.0.0",
+    "type": "range"
+  },
+  "_requiredBy": [
+    "#USER",
+    "/"
+  ],
+  "_resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.2.tgz",
+  "_shasum": "c54e9aac57e432875233525f3c891c4159ffefd7",
+  "_shrinkwrap": null,
+  "_spec": "node-fetch@^1.7.2",
+  "_where": "/home/nate/Desktop/tar_JDkx6AzcE",
+  "author": {
+    "name": "David Frank"
+  },
+  "bugs": {
+    "url": "https://github.com/bitinn/node-fetch/issues"
+  },
+  "dependencies": {
+    "encoding": "^0.1.11",
+    "is-stream": "^1.0.1"
+  },
+  "description": "A light-weight module that brings window.fetch to node.js and io.js",
+  "devDependencies": {
+    "bluebird": "^3.3.4",
+    "chai": "^3.5.0",
+    "chai-as-promised": "^5.2.0",
+    "codecov": "^1.0.1",
+    "form-data": ">=1.0.0",
+    "istanbul": "^0.4.2",
+    "mocha": "^2.1.0",
+    "parted": "^0.1.1",
+    "promise": "^7.1.1",
+    "resumer": "0.0.0"
+  },
+  "directories": {},
+  "dist": {
+    "integrity": "sha512-xZZUq2yDhKMIn/UgG5q//IZSNLJIwW2QxS14CNH5spuiXkITM2pUitjdq58yLSaU7m4M0wBNaM2Gh/ggY4YJig==",
+    "shasum": "c54e9aac57e432875233525f3c891c4159ffefd7",
+    "tarball": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.2.tgz"
+  },
+  "gitHead": "3c9b64caf5c84f96052078f18d91c9e12bd5f5c6",
+  "homepage": "https://github.com/bitinn/node-fetch",
+  "keywords": [
+    "fetch",
+    "http",
+    "promise"
+  ],
+  "license": "MIT",
+  "main": "index.js",
+  "maintainers": [
+    {
+      "name": "bitinn",
+      "email": "bitinn@gmail.com"
+    },
+    {
+      "name": "timothygu",
+      "email": "timothygu99@gmail.com"
+    }
+  ],
+  "name": "node-fetch",
+  "optionalDependencies": {},
+  "readme": "ERROR: No README data found!",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/bitinn/node-fetch.git"
+  },
+  "scripts": {
+    "coverage": "istanbul cover _mocha --report lcovonly -- -R spec test/test.js && codecov",
+    "report": "istanbul cover _mocha -- -R spec test/test.js",
+    "test": "mocha test/test.js"
+  },
+  "version": "1.7.2"
+}
diff --git a/hash_script/node_modules/node-fetch/test/dummy.txt b/hash_script/node_modules/node-fetch/test/dummy.txt
new file mode 100644
index 0000000..5ca5191
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/test/dummy.txt
@@ -0,0 +1 @@
+i am a dummy
\ No newline at end of file
diff --git a/hash_script/node_modules/node-fetch/test/server.js b/hash_script/node_modules/node-fetch/test/server.js
new file mode 100644
index 0000000..5b1b3b9
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/test/server.js
@@ -0,0 +1,340 @@
+
+var http = require('http');
+var parse = require('url').parse;
+var zlib = require('zlib');
+var stream = require('stream');
+var convert = require('encoding').convert;
+var Multipart = require('parted').multipart;
+
+module.exports = TestServer;
+
+function TestServer() {
+	this.server = http.createServer(this.router);
+	this.port = 30001;
+	this.hostname = 'localhost';
+	// node 8 default keepalive timeout is 5000ms
+	// make it shorter here as we want to close server quickly at the end of tests
+	this.server.keepAliveTimeout = 1000;
+	this.server.on('error', function(err) {
+		console.log(err.stack);
+	});
+	this.server.on('connection', function(socket) {
+		socket.setTimeout(1500);
+	});
+}
+
+TestServer.prototype.start = function(cb) {
+	this.server.listen(this.port, this.hostname, cb);
+}
+
+TestServer.prototype.stop = function(cb) {
+	this.server.close(cb);
+}
+
+TestServer.prototype.router = function(req, res) {
+
+	var p = parse(req.url).pathname;
+
+	if (p === '/hello') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.end('world');
+	}
+
+	if (p === '/plain') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.end('text');
+	}
+
+	if (p === '/options') {
+		res.statusCode = 200;
+		res.setHeader('Allow', 'GET, HEAD, OPTIONS');
+		res.end('hello world');
+	}
+
+	if (p === '/html') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/html');
+		res.end('<html></html>');
+	}
+
+	if (p === '/json') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'application/json');
+		res.end(JSON.stringify({
+			name: 'value'
+		}));
+	}
+
+	if (p === '/gzip') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.setHeader('Content-Encoding', 'gzip');
+		zlib.gzip('hello world', function(err, buffer) {
+			res.end(buffer);
+		});
+	}
+
+	if (p === '/deflate') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.setHeader('Content-Encoding', 'deflate');
+		zlib.deflate('hello world', function(err, buffer) {
+			res.end(buffer);
+		});
+	}
+
+	if (p === '/deflate-raw') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.setHeader('Content-Encoding', 'deflate');
+		zlib.deflateRaw('hello world', function(err, buffer) {
+			res.end(buffer);
+		});
+	}
+
+	if (p === '/sdch') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.setHeader('Content-Encoding', 'sdch');
+		res.end('fake sdch string');
+	}
+
+	if (p === '/invalid-content-encoding') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.setHeader('Content-Encoding', 'gzip');
+		res.end('fake gzip string');
+	}
+
+	if (p === '/timeout') {
+		setTimeout(function() {
+			res.statusCode = 200;
+			res.setHeader('Content-Type', 'text/plain');
+			res.end('text');
+		}, 1000);
+	}
+
+	if (p === '/slow') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.write('test');
+		setTimeout(function() {
+			res.end('test');
+		}, 1000);
+	}
+
+	if (p === '/cookie') {
+		res.statusCode = 200;
+		res.setHeader('Set-Cookie', ['a=1', 'b=1']);
+		res.end('cookie');
+	}
+
+	if (p === '/size/chunk') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		setTimeout(function() {
+			res.write('test');
+		}, 50);
+		setTimeout(function() {
+			res.end('test');
+		}, 100);
+	}
+
+	if (p === '/size/long') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain');
+		res.end('testtest');
+	}
+
+	if (p === '/encoding/gbk') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/html');
+		res.end(convert('<meta charset="gbk"><div>中文</div>', 'gbk'));
+	}
+
+	if (p === '/encoding/gb2312') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/html');
+		res.end(convert('<meta http-equiv="Content-Type" content="text/html; charset=gb2312"><div>中文</div>', 'gb2312'));
+	}
+
+	if (p === '/encoding/shift-jis') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/html; charset=Shift-JIS');
+		res.end(convert('<div>日本語</div>', 'Shift_JIS'));
+	}
+
+	if (p === '/encoding/euc-jp') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/xml');
+		res.end(convert('<?xml version="1.0" encoding="EUC-JP"?><title>日本語</title>', 'EUC-JP'));
+	}
+
+	if (p === '/encoding/utf8') {
+		res.statusCode = 200;
+		res.end('中文');
+	}
+
+	if (p === '/encoding/order1') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'charset=gbk; text/plain');
+		res.end(convert('中文', 'gbk'));
+	}
+
+	if (p === '/encoding/order2') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/plain; charset=gbk; qs=1');
+		res.end(convert('中文', 'gbk'));
+	}
+
+	if (p === '/encoding/chunked') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/html');
+		res.setHeader('Transfer-Encoding', 'chunked');
+		var padding = 'a';
+		for (var i = 0; i < 10; i++) {
+			res.write(padding);
+		}
+		res.end(convert('<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" /><div>日本語</div>', 'Shift_JIS'));
+	}
+
+	if (p === '/encoding/invalid') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'text/html');
+		res.setHeader('Transfer-Encoding', 'chunked');
+		// because node v0.12 doesn't have str.repeat
+		var padding = new Array(120 + 1).join('a');
+		for (var i = 0; i < 10; i++) {
+			res.write(padding);
+		}
+		res.end(convert('中文', 'gbk'));
+	}
+
+	if (p === '/redirect/301') {
+		res.statusCode = 301;
+		res.setHeader('Location', '/inspect');
+		res.end();
+	}
+
+	if (p === '/redirect/302') {
+		res.statusCode = 302;
+		res.setHeader('Location', '/inspect');
+		res.end();
+	}
+
+	if (p === '/redirect/303') {
+		res.statusCode = 303;
+		res.setHeader('Location', '/inspect');
+		res.end();
+	}
+
+	if (p === '/redirect/307') {
+		res.statusCode = 307;
+		res.setHeader('Location', '/inspect');
+		res.end();
+	}
+
+	if (p === '/redirect/308') {
+		res.statusCode = 308;
+		res.setHeader('Location', '/inspect');
+		res.end();
+	}
+
+	if (p === '/redirect/chain') {
+		res.statusCode = 301;
+		res.setHeader('Location', '/redirect/301');
+		res.end();
+	}
+
+	if (p === '/error/redirect') {
+		res.statusCode = 301;
+		//res.setHeader('Location', '/inspect');
+		res.end();
+	}
+
+	if (p === '/error/400') {
+		res.statusCode = 400;
+		res.setHeader('Content-Type', 'text/plain');
+		res.end('client error');
+	}
+
+	if (p === '/error/404') {
+		res.statusCode = 404;
+		res.setHeader('Content-Encoding', 'gzip');
+		res.end();
+	}
+
+	if (p === '/error/500') {
+		res.statusCode = 500;
+		res.setHeader('Content-Type', 'text/plain');
+		res.end('server error');
+	}
+
+	if (p === '/error/reset') {
+		res.destroy();
+	}
+
+	if (p === '/error/json') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'application/json');
+		res.end('invalid json');
+	}
+
+	if (p === '/no-content') {
+		res.statusCode = 204;
+		res.end();
+	}
+
+	if (p === '/no-content/gzip') {
+		res.statusCode = 204;
+		res.setHeader('Content-Encoding', 'gzip');
+		res.end();
+	}
+
+	if (p === '/not-modified') {
+		res.statusCode = 304;
+		res.end();
+	}
+
+	if (p === '/not-modified/gzip') {
+		res.statusCode = 304;
+		res.setHeader('Content-Encoding', 'gzip');
+		res.end();
+	}
+
+	if (p === '/inspect') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'application/json');
+		var body = '';
+		req.on('data', function(c) { body += c });
+		req.on('end', function() {
+			res.end(JSON.stringify({
+				method: req.method,
+				url: req.url,
+				headers: req.headers,
+				body: body
+			}));
+		});
+	}
+
+	if (p === '/multipart') {
+		res.statusCode = 200;
+		res.setHeader('Content-Type', 'application/json');
+		var parser = new Multipart(req.headers['content-type']);
+		var body = '';
+		parser.on('part', function(field, part) {
+			body += field + '=' + part;
+		});
+		parser.on('end', function() {
+			res.end(JSON.stringify({
+				method: req.method,
+				url: req.url,
+				headers: req.headers,
+				body: body
+			}));
+		});
+		req.pipe(parser);
+	}
+}
diff --git a/hash_script/node_modules/node-fetch/test/test.js b/hash_script/node_modules/node-fetch/test/test.js
new file mode 100644
index 0000000..284b263
--- /dev/null
+++ b/hash_script/node_modules/node-fetch/test/test.js
@@ -0,0 +1,1487 @@
+
+// test tools
+var chai = require('chai');
+var cap = require('chai-as-promised');
+chai.use(cap);
+var expect = chai.expect;
+var bluebird = require('bluebird');
+var then = require('promise');
+var spawn = require('child_process').spawn;
+var stream = require('stream');
+var resumer = require('resumer');
+var FormData = require('form-data');
+var http = require('http');
+var fs = require('fs');
+
+var TestServer = require('./server');
+
+// test subjects
+var fetch = require('../index.js');
+var Headers = require('../lib/headers.js');
+var Response = require('../lib/response.js');
+var Request = require('../lib/request.js');
+var Body = require('../lib/body.js');
+var FetchError = require('../lib/fetch-error.js');
+// test with native promise on node 0.11, and bluebird for node 0.10
+fetch.Promise = fetch.Promise || bluebird;
+
+var url, opts, local, base;
+
+describe('node-fetch', function() {
+
+	before(function(done) {
+		local = new TestServer();
+		base = 'http://' + local.hostname + ':' + local.port;
+		local.start(done);
+	});
+
+	after(function(done) {
+		local.stop(done);
+	});
+
+	it('should return a promise', function() {
+		url = 'http://example.com/';
+		var p = fetch(url);
+		expect(p).to.be.an.instanceof(fetch.Promise);
+		expect(p).to.have.property('then');
+	});
+
+	it('should allow custom promise', function() {
+		url = 'http://example.com/';
+		var old = fetch.Promise;
+		fetch.Promise = then;
+		expect(fetch(url)).to.be.an.instanceof(then);
+		expect(fetch(url)).to.not.be.an.instanceof(bluebird);
+		fetch.Promise = old;
+	});
+
+	it('should throw error when no promise implementation are found', function() {
+		url = 'http://example.com/';
+		var old = fetch.Promise;
+		fetch.Promise = undefined;
+		expect(function() {
+			fetch(url)
+		}).to.throw(Error);
+		fetch.Promise = old;
+	});
+
+	it('should expose Headers, Response and Request constructors', function() {
+		expect(fetch.Headers).to.equal(Headers);
+		expect(fetch.Response).to.equal(Response);
+		expect(fetch.Request).to.equal(Request);
+	});
+
+	it('should reject with error if url is protocol relative', function() {
+		url = '//example.com/';
+		return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
+	});
+
+	it('should reject with error if url is relative path', function() {
+		url = '/some/path';
+		return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
+	});
+
+	it('should reject with error if protocol is unsupported', function() {
+		url = 'ftp://example.com/';
+		return expect(fetch(url)).to.eventually.be.rejectedWith(Error);
+	});
+
+	it('should reject with error on network failure', function() {
+		url = 'http://localhost:50000/';
+		return expect(fetch(url)).to.eventually.be.rejected
+			.and.be.an.instanceOf(FetchError)
+			.and.include({ type: 'system', code: 'ECONNREFUSED', errno: 'ECONNREFUSED' });
+	});
+
+	it('should resolve into response', function() {
+		url = base + '/hello';
+		return fetch(url).then(function(res) {
+			expect(res).to.be.an.instanceof(Response);
+			expect(res.headers).to.be.an.instanceof(Headers);
+			expect(res.body).to.be.an.instanceof(stream.Transform);
+			expect(res.bodyUsed).to.be.false;
+
+			expect(res.url).to.equal(url);
+			expect(res.ok).to.be.true;
+			expect(res.status).to.equal(200);
+			expect(res.statusText).to.equal('OK');
+		});
+	});
+
+	it('should accept plain text response', function() {
+		url = base + '/plain';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return res.text().then(function(result) {
+				expect(res.bodyUsed).to.be.true;
+				expect(result).to.be.a('string');
+				expect(result).to.equal('text');
+			});
+		});
+	});
+
+	it('should accept html response (like plain text)', function() {
+		url = base + '/html';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/html');
+			return res.text().then(function(result) {
+				expect(res.bodyUsed).to.be.true;
+				expect(result).to.be.a('string');
+				expect(result).to.equal('<html></html>');
+			});
+		});
+	});
+
+	it('should accept json response', function() {
+		url = base + '/json';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('application/json');
+			return res.json().then(function(result) {
+				expect(res.bodyUsed).to.be.true;
+				expect(result).to.be.an('object');
+				expect(result).to.deep.equal({ name: 'value' });
+			});
+		});
+	});
+
+	it('should send request with custom headers', function() {
+		url = base + '/inspect';
+		opts = {
+			headers: { 'x-custom-header': 'abc' }
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.headers['x-custom-header']).to.equal('abc');
+		});
+	});
+
+	it('should accept headers instance', function() {
+		url = base + '/inspect';
+		opts = {
+			headers: new Headers({ 'x-custom-header': 'abc' })
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.headers['x-custom-header']).to.equal('abc');
+		});
+	});
+
+	it('should accept custom host header', function() {
+		url = base + '/inspect';
+		opts = {
+			headers: {
+				host: 'example.com'
+			}
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.headers['host']).to.equal('example.com');
+		});
+	});
+
+	it('should follow redirect code 301', function() {
+		url = base + '/redirect/301';
+		return fetch(url).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+			expect(res.ok).to.be.true;
+		});
+	});
+
+	it('should follow redirect code 302', function() {
+		url = base + '/redirect/302';
+		return fetch(url).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+		});
+	});
+
+	it('should follow redirect code 303', function() {
+		url = base + '/redirect/303';
+		return fetch(url).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+		});
+	});
+
+	it('should follow redirect code 307', function() {
+		url = base + '/redirect/307';
+		return fetch(url).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+		});
+	});
+
+	it('should follow redirect code 308', function() {
+		url = base + '/redirect/308';
+		return fetch(url).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+		});
+	});
+
+	it('should follow redirect chain', function() {
+		url = base + '/redirect/chain';
+		return fetch(url).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+		});
+	});
+
+	it('should follow POST request redirect code 301 with GET', function() {
+		url = base + '/redirect/301';
+		opts = {
+			method: 'POST'
+			, body: 'a=1'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+			return res.json().then(function(result) {
+				expect(result.method).to.equal('GET');
+				expect(result.body).to.equal('');
+			});
+		});
+	});
+
+	it('should follow POST request redirect code 302 with GET', function() {
+		url = base + '/redirect/302';
+		opts = {
+			method: 'POST'
+			, body: 'a=1'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+			return res.json().then(function(result) {
+				expect(result.method).to.equal('GET');
+				expect(result.body).to.equal('');
+			});
+		});
+	});
+
+	it('should follow redirect code 303 with GET', function() {
+		url = base + '/redirect/303';
+		opts = {
+			method: 'PUT'
+			, body: 'a=1'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+			return res.json().then(function(result) {
+				expect(result.method).to.equal('GET');
+				expect(result.body).to.equal('');
+			});
+		});
+	});
+
+	it('should obey maximum redirect, reject case', function() {
+		url = base + '/redirect/chain';
+		opts = {
+			follow: 1
+		}
+		return expect(fetch(url, opts)).to.eventually.be.rejected
+			.and.be.an.instanceOf(FetchError)
+			.and.have.property('type', 'max-redirect');
+	});
+
+	it('should obey redirect chain, resolve case', function() {
+		url = base + '/redirect/chain';
+		opts = {
+			follow: 2
+		}
+		return fetch(url, opts).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			expect(res.status).to.equal(200);
+		});
+	});
+
+	it('should allow not following redirect', function() {
+		url = base + '/redirect/301';
+		opts = {
+			follow: 0
+		}
+		return expect(fetch(url, opts)).to.eventually.be.rejected
+			.and.be.an.instanceOf(FetchError)
+			.and.have.property('type', 'max-redirect');
+	});
+
+	it('should support redirect mode, manual flag', function() {
+		url = base + '/redirect/301';
+		opts = {
+			redirect: 'manual'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.url).to.equal(url);
+			expect(res.status).to.equal(301);
+			expect(res.headers.get('location')).to.equal(base + '/inspect');
+		});
+	});
+
+	it('should support redirect mode, error flag', function() {
+		url = base + '/redirect/301';
+		opts = {
+			redirect: 'error'
+		};
+		return expect(fetch(url, opts)).to.eventually.be.rejected
+			.and.be.an.instanceOf(FetchError)
+			.and.have.property('type', 'no-redirect');
+	});
+
+	it('should support redirect mode, manual flag when there is no redirect', function() {
+		url = base + '/hello';
+		opts = {
+			redirect: 'manual'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.url).to.equal(url);
+			expect(res.status).to.equal(200);
+			expect(res.headers.get('location')).to.be.null;
+		});
+	});
+
+	it('should follow redirect code 301 and keep existing headers', function() {
+		url = base + '/redirect/301';
+		opts = {
+			headers: new Headers({ 'x-custom-header': 'abc' })
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.url).to.equal(base + '/inspect');
+			return res.json();
+		}).then(function(res) {
+			expect(res.headers['x-custom-header']).to.equal('abc');
+		});
+	});
+
+	it('should reject broken redirect', function() {
+		url = base + '/error/redirect';
+		return expect(fetch(url)).to.eventually.be.rejected
+			.and.be.an.instanceOf(FetchError)
+			.and.have.property('type', 'invalid-redirect');
+	});
+
+	it('should not reject broken redirect under manual redirect', function() {
+		url = base + '/error/redirect';
+		opts = {
+			redirect: 'manual'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.url).to.equal(url);
+			expect(res.status).to.equal(301);
+			expect(res.headers.get('location')).to.be.null;
+		});
+	});
+
+	it('should handle client-error response', function() {
+		url = base + '/error/400';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			expect(res.status).to.equal(400);
+			expect(res.statusText).to.equal('Bad Request');
+			expect(res.ok).to.be.false;
+			return res.text().then(function(result) {
+				expect(res.bodyUsed).to.be.true;
+				expect(result).to.be.a('string');
+				expect(result).to.equal('client error');
+			});
+		});
+	});
+
+	it('should handle server-error response', function() {
+		url = base + '/error/500';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			expect(res.status).to.equal(500);
+			expect(res.statusText).to.equal('Internal Server Error');
+			expect(res.ok).to.be.false;
+			return res.text().then(function(result) {
+				expect(res.bodyUsed).to.be.true;
+				expect(result).to.be.a('string');
+				expect(result).to.equal('server error');
+			});
+		});
+	});
+
+	it('should handle network-error response', function() {
+		url = base + '/error/reset';
+		return expect(fetch(url)).to.eventually.be.rejected
+			.and.be.an.instanceOf(FetchError)
+			.and.have.property('code', 'ECONNRESET');
+	});
+
+	it('should handle DNS-error response', function() {
+		url = 'http://domain.invalid';
+		return expect(fetch(url)).to.eventually.be.rejected
+			.and.be.an.instanceOf(FetchError)
+			.and.have.property('code', 'ENOTFOUND');
+	});
+
+	it('should reject invalid json response', function() {
+		url = base + '/error/json';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('application/json');
+			return expect(res.json()).to.eventually.be.rejectedWith(Error);
+		});
+	});
+
+	it('should handle no content response', function() {
+		url = base + '/no-content';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(204);
+			expect(res.statusText).to.equal('No Content');
+			expect(res.ok).to.be.true;
+			return res.text().then(function(result) {
+				expect(result).to.be.a('string');
+				expect(result).to.be.empty;
+			});
+		});
+	});
+
+	it('should throw on no-content json response', function() {
+		url = base + '/no-content';
+		return fetch(url).then(function(res) {
+			return expect(res.json()).to.eventually.be.rejectedWith(FetchError);
+		});
+	});
+
+	it('should handle no content response with gzip encoding', function() {
+		url = base + '/no-content/gzip';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(204);
+			expect(res.statusText).to.equal('No Content');
+			expect(res.headers.get('content-encoding')).to.equal('gzip');
+			expect(res.ok).to.be.true;
+			return res.text().then(function(result) {
+				expect(result).to.be.a('string');
+				expect(result).to.be.empty;
+			});
+		});
+	});
+
+	it('should handle not modified response', function() {
+		url = base + '/not-modified';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(304);
+			expect(res.statusText).to.equal('Not Modified');
+			expect(res.ok).to.be.false;
+			return res.text().then(function(result) {
+				expect(result).to.be.a('string');
+				expect(result).to.be.empty;
+			});
+		});
+	});
+
+	it('should handle not modified response with gzip encoding', function() {
+		url = base + '/not-modified/gzip';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(304);
+			expect(res.statusText).to.equal('Not Modified');
+			expect(res.headers.get('content-encoding')).to.equal('gzip');
+			expect(res.ok).to.be.false;
+			return res.text().then(function(result) {
+				expect(result).to.be.a('string');
+				expect(result).to.be.empty;
+			});
+		});
+	});
+
+	it('should decompress gzip response', function() {
+		url = base + '/gzip';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return res.text().then(function(result) {
+				expect(result).to.be.a('string');
+				expect(result).to.equal('hello world');
+			});
+		});
+	});
+
+	it('should decompress deflate response', function() {
+		url = base + '/deflate';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return res.text().then(function(result) {
+				expect(result).to.be.a('string');
+				expect(result).to.equal('hello world');
+			});
+		});
+	});
+
+	it('should decompress deflate raw response from old apache server', function() {
+		url = base + '/deflate-raw';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return res.text().then(function(result) {
+				expect(result).to.be.a('string');
+				expect(result).to.equal('hello world');
+			});
+		});
+	});
+
+	it('should skip decompression if unsupported', function() {
+		url = base + '/sdch';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return res.text().then(function(result) {
+				expect(result).to.be.a('string');
+				expect(result).to.equal('fake sdch string');
+			});
+		});
+	});
+
+	it('should reject if response compression is invalid', function() {
+		url = base + '/invalid-content-encoding';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return expect(res.text()).to.eventually.be.rejected
+				.and.be.an.instanceOf(FetchError)
+				.and.have.property('code', 'Z_DATA_ERROR');
+		});
+	});
+
+	it('should allow disabling auto decompression', function() {
+		url = base + '/gzip';
+		opts = {
+			compress: false
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return res.text().then(function(result) {
+				expect(result).to.be.a('string');
+				expect(result).to.not.equal('hello world');
+			});
+		});
+	});
+
+	it('should allow custom timeout', function() {
+		this.timeout(500);
+		url = base + '/timeout';
+		opts = {
+			timeout: 100
+		};
+		return expect(fetch(url, opts)).to.eventually.be.rejected
+			.and.be.an.instanceOf(FetchError)
+			.and.have.property('type', 'request-timeout');
+	});
+
+	it('should allow custom timeout on response body', function() {
+		this.timeout(500);
+		url = base + '/slow';
+		opts = {
+			timeout: 100
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.ok).to.be.true;
+			return expect(res.text()).to.eventually.be.rejected
+				.and.be.an.instanceOf(FetchError)
+				.and.have.property('type', 'body-timeout');
+		});
+	});
+
+	it('should clear internal timeout on fetch response', function (done) {
+		this.timeout(1000);
+		spawn('node', ['-e', 'require("./")("' + base + '/hello", { timeout: 5000 })'])
+			.on('exit', function () {
+				done();
+			});
+	});
+
+	it('should clear internal timeout on fetch redirect', function (done) {
+		this.timeout(1000);
+		spawn('node', ['-e', 'require("./")("' + base + '/redirect/301", { timeout: 5000 })'])
+			.on('exit', function () {
+				done();
+			});
+	});
+
+	it('should clear internal timeout on fetch error', function (done) {
+		this.timeout(1000);
+		spawn('node', ['-e', 'require("./")("' + base + '/error/reset", { timeout: 5000 })'])
+			.on('exit', function () {
+				done();
+			});
+	});
+
+	it('should allow POST request', function() {
+		url = base + '/inspect';
+		opts = {
+			method: 'POST'
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('POST');
+			expect(res.headers['transfer-encoding']).to.be.undefined;
+			expect(res.headers['content-length']).to.equal('0');
+		});
+	});
+
+	it('should allow POST request with string body', function() {
+		url = base + '/inspect';
+		opts = {
+			method: 'POST'
+			, body: 'a=1'
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('POST');
+			expect(res.body).to.equal('a=1');
+			expect(res.headers['transfer-encoding']).to.be.undefined;
+			expect(res.headers['content-length']).to.equal('3');
+		});
+	});
+
+	it('should allow POST request with buffer body', function() {
+		url = base + '/inspect';
+		opts = {
+			method: 'POST'
+			, body: new Buffer('a=1', 'utf-8')
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('POST');
+			expect(res.body).to.equal('a=1');
+			expect(res.headers['transfer-encoding']).to.equal('chunked');
+			expect(res.headers['content-length']).to.be.undefined;
+		});
+	});
+
+	it('should allow POST request with readable stream as body', function() {
+		var body = resumer().queue('a=1').end();
+		body = body.pipe(new stream.PassThrough());
+
+		url = base + '/inspect';
+		opts = {
+			method: 'POST'
+			, body: body
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('POST');
+			expect(res.body).to.equal('a=1');
+			expect(res.headers['transfer-encoding']).to.equal('chunked');
+			expect(res.headers['content-length']).to.be.undefined;
+		});
+	});
+
+	it('should allow POST request with form-data as body', function() {
+		var form = new FormData();
+		form.append('a','1');
+
+		url = base + '/multipart';
+		opts = {
+			method: 'POST'
+			, body: form
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('POST');
+			expect(res.headers['content-type']).to.contain('multipart/form-data');
+			expect(res.headers['content-length']).to.be.a('string');
+			expect(res.body).to.equal('a=1');
+		});
+	});
+
+	it('should allow POST request with form-data using stream as body', function() {
+		var form = new FormData();
+		form.append('my_field', fs.createReadStream('test/dummy.txt'));
+
+		url = base + '/multipart';
+		opts = {
+			method: 'POST'
+			, body: form
+		};
+
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('POST');
+			expect(res.headers['content-type']).to.contain('multipart/form-data');
+			expect(res.headers['content-length']).to.be.undefined;
+			expect(res.body).to.contain('my_field=');
+		});
+	});
+
+	it('should allow POST request with form-data as body and custom headers', function() {
+		var form = new FormData();
+		form.append('a','1');
+
+		var headers = form.getHeaders();
+		headers['b'] = '2';
+
+		url = base + '/multipart';
+		opts = {
+			method: 'POST'
+			, body: form
+			, headers: headers
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('POST');
+			expect(res.headers['content-type']).to.contain('multipart/form-data');
+			expect(res.headers['content-length']).to.be.a('string');
+			expect(res.headers.b).to.equal('2');
+			expect(res.body).to.equal('a=1');
+		});
+	});
+
+	it('should allow POST request with object body', function() {
+		url = base + '/inspect';
+		// note that fetch simply calls tostring on an object
+		opts = {
+			method: 'POST'
+			, body: { a:1 }
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('POST');
+			expect(res.body).to.equal('[object Object]');
+		});
+	});
+
+	it('should allow PUT request', function() {
+		url = base + '/inspect';
+		opts = {
+			method: 'PUT'
+			, body: 'a=1'
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('PUT');
+			expect(res.body).to.equal('a=1');
+		});
+	});
+
+	it('should allow DELETE request', function() {
+		url = base + '/inspect';
+		opts = {
+			method: 'DELETE'
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('DELETE');
+		});
+	});
+
+	it('should allow POST request with string body', function() {
+		url = base + '/inspect';
+		opts = {
+			method: 'POST'
+			, body: 'a=1'
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('POST');
+			expect(res.body).to.equal('a=1');
+			expect(res.headers['transfer-encoding']).to.be.undefined;
+			expect(res.headers['content-length']).to.equal('3');
+		});
+	});
+
+	it('should allow DELETE request with string body', function() {
+		url = base + '/inspect';
+		opts = {
+			method: 'DELETE'
+			, body: 'a=1'
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('DELETE');
+			expect(res.body).to.equal('a=1');
+			expect(res.headers['transfer-encoding']).to.be.undefined;
+			expect(res.headers['content-length']).to.equal('3');
+		});
+	});
+
+	it('should allow PATCH request', function() {
+		url = base + '/inspect';
+		opts = {
+			method: 'PATCH'
+			, body: 'a=1'
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.method).to.equal('PATCH');
+			expect(res.body).to.equal('a=1');
+		});
+	});
+
+	it('should allow HEAD request', function() {
+		url = base + '/hello';
+		opts = {
+			method: 'HEAD'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.status).to.equal(200);
+			expect(res.statusText).to.equal('OK');
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			expect(res.body).to.be.an.instanceof(stream.Transform);
+			return res.text();
+		}).then(function(text) {
+			expect(text).to.equal('');
+		});
+	});
+
+	it('should allow HEAD request with content-encoding header', function() {
+		url = base + '/error/404';
+		opts = {
+			method: 'HEAD'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.status).to.equal(404);
+			expect(res.headers.get('content-encoding')).to.equal('gzip');
+			return res.text();
+		}).then(function(text) {
+			expect(text).to.equal('');
+		});
+	});
+
+	it('should allow OPTIONS request', function() {
+		url = base + '/options';
+		opts = {
+			method: 'OPTIONS'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.status).to.equal(200);
+			expect(res.statusText).to.equal('OK');
+			expect(res.headers.get('allow')).to.equal('GET, HEAD, OPTIONS');
+			expect(res.body).to.be.an.instanceof(stream.Transform);
+		});
+	});
+
+	it('should reject decoding body twice', function() {
+		url = base + '/plain';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return res.text().then(function(result) {
+				expect(res.bodyUsed).to.be.true;
+				return expect(res.text()).to.eventually.be.rejectedWith(Error);
+			});
+		});
+	});
+
+	it('should support maximum response size, multiple chunk', function() {
+		url = base + '/size/chunk';
+		opts = {
+			size: 5
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.status).to.equal(200);
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return expect(res.text()).to.eventually.be.rejected
+				.and.be.an.instanceOf(FetchError)
+				.and.have.property('type', 'max-size');
+		});
+	});
+
+	it('should support maximum response size, single chunk', function() {
+		url = base + '/size/long';
+		opts = {
+			size: 5
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.status).to.equal(200);
+			expect(res.headers.get('content-type')).to.equal('text/plain');
+			return expect(res.text()).to.eventually.be.rejected
+				.and.be.an.instanceOf(FetchError)
+				.and.have.property('type', 'max-size');
+		});
+	});
+
+	it('should support encoding decode, xml dtd detect', function() {
+		url = base + '/encoding/euc-jp';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(200);
+			return res.text().then(function(result) {
+				expect(result).to.equal('<?xml version="1.0" encoding="EUC-JP"?><title>日本語</title>');
+			});
+		});
+	});
+
+	it('should support encoding decode, content-type detect', function() {
+		url = base + '/encoding/shift-jis';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(200);
+			return res.text().then(function(result) {
+				expect(result).to.equal('<div>日本語</div>');
+			});
+		});
+	});
+
+	it('should support encoding decode, html5 detect', function() {
+		url = base + '/encoding/gbk';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(200);
+			return res.text().then(function(result) {
+				expect(result).to.equal('<meta charset="gbk"><div>中文</div>');
+			});
+		});
+	});
+
+	it('should support encoding decode, html4 detect', function() {
+		url = base + '/encoding/gb2312';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(200);
+			return res.text().then(function(result) {
+				expect(result).to.equal('<meta http-equiv="Content-Type" content="text/html; charset=gb2312"><div>中文</div>');
+			});
+		});
+	});
+
+	it('should default to utf8 encoding', function() {
+		url = base + '/encoding/utf8';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(200);
+			expect(res.headers.get('content-type')).to.be.null;
+			return res.text().then(function(result) {
+				expect(result).to.equal('中文');
+			});
+		});
+	});
+
+	it('should support uncommon content-type order, charset in front', function() {
+		url = base + '/encoding/order1';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(200);
+			return res.text().then(function(result) {
+				expect(result).to.equal('中文');
+			});
+		});
+	});
+
+	it('should support uncommon content-type order, end with qs', function() {
+		url = base + '/encoding/order2';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(200);
+			return res.text().then(function(result) {
+				expect(result).to.equal('中文');
+			});
+		});
+	});
+
+	it('should support chunked encoding, html4 detect', function() {
+		url = base + '/encoding/chunked';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(200);
+			// because node v0.12 doesn't have str.repeat
+			var padding = new Array(10 + 1).join('a');
+			return res.text().then(function(result) {
+				expect(result).to.equal(padding + '<meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS" /><div>日本語</div>');
+			});
+		});
+	});
+
+	it('should only do encoding detection up to 1024 bytes', function() {
+		url = base + '/encoding/invalid';
+		return fetch(url).then(function(res) {
+			expect(res.status).to.equal(200);
+			// because node v0.12 doesn't have str.repeat
+			var padding = new Array(1200 + 1).join('a');
+			return res.text().then(function(result) {
+				expect(result).to.not.equal(padding + '中文');
+			});
+		});
+	});
+
+	it('should allow piping response body as stream', function(done) {
+		url = base + '/hello';
+		fetch(url).then(function(res) {
+			expect(res.body).to.be.an.instanceof(stream.Transform);
+			res.body.on('data', function(chunk) {
+				if (chunk === null) {
+					return;
+				}
+				expect(chunk.toString()).to.equal('world');
+			});
+			res.body.on('end', function() {
+				done();
+			});
+		});
+	});
+
+	it('should allow cloning a response, and use both as stream', function(done) {
+		url = base + '/hello';
+		return fetch(url).then(function(res) {
+			var counter = 0;
+			var r1 = res.clone();
+			expect(res.body).to.be.an.instanceof(stream.Transform);
+			expect(r1.body).to.be.an.instanceof(stream.Transform);
+			res.body.on('data', function(chunk) {
+				if (chunk === null) {
+					return;
+				}
+				expect(chunk.toString()).to.equal('world');
+			});
+			res.body.on('end', function() {
+				counter++;
+				if (counter == 2) {
+					done();
+				}
+			});
+			r1.body.on('data', function(chunk) {
+				if (chunk === null) {
+					return;
+				}
+				expect(chunk.toString()).to.equal('world');
+			});
+			r1.body.on('end', function() {
+				counter++;
+				if (counter == 2) {
+					done();
+				}
+			});
+		});
+	});
+
+	it('should allow cloning a json response and log it as text response', function() {
+		url = base + '/json';
+		return fetch(url).then(function(res) {
+			var r1 = res.clone();
+			return fetch.Promise.all([res.json(), r1.text()]).then(function(results) {
+				expect(results[0]).to.deep.equal({name: 'value'});
+				expect(results[1]).to.equal('{"name":"value"}');
+			});
+		});
+	});
+
+	it('should allow cloning a json response, and then log it as text response', function() {
+		url = base + '/json';
+		return fetch(url).then(function(res) {
+			var r1 = res.clone();
+			return res.json().then(function(result) {
+				expect(result).to.deep.equal({name: 'value'});
+				return r1.text().then(function(result) {
+					expect(result).to.equal('{"name":"value"}');
+				});
+			});
+		});
+	});
+
+	it('should allow cloning a json response, first log as text response, then return json object', function() {
+		url = base + '/json';
+		return fetch(url).then(function(res) {
+			var r1 = res.clone();
+			return r1.text().then(function(result) {
+				expect(result).to.equal('{"name":"value"}');
+				return res.json().then(function(result) {
+					expect(result).to.deep.equal({name: 'value'});
+				});
+			});
+		});
+	});
+
+	it('should not allow cloning a response after its been used', function() {
+		url = base + '/hello';
+		return fetch(url).then(function(res) {
+			return res.text().then(function(result) {
+				expect(function() {
+					var r1 = res.clone();
+				}).to.throw(Error);
+			});
+		})
+	});
+
+	it('should allow get all responses of a header', function() {
+		url = base + '/cookie';
+		return fetch(url).then(function(res) {
+			expect(res.headers.get('set-cookie')).to.equal('a=1');
+			expect(res.headers.get('Set-Cookie')).to.equal('a=1');
+			expect(res.headers.getAll('set-cookie')).to.deep.equal(['a=1', 'b=1']);
+			expect(res.headers.getAll('Set-Cookie')).to.deep.equal(['a=1', 'b=1']);
+		});
+	});
+
+	it('should allow iterating through all headers', function() {
+		var headers = new Headers({
+			a: 1
+			, b: [2, 3]
+			, c: [4]
+		});
+		expect(headers).to.have.property('forEach');
+
+		var result = [];
+		headers.forEach(function(val, key) {
+			result.push([key, val]);
+		});
+
+		expected = [
+			["a", "1"]
+			, ["b", "2"]
+			, ["b", "3"]
+			, ["c", "4"]
+		];
+		expect(result).to.deep.equal(expected);
+	});
+
+	it('should allow deleting header', function() {
+		url = base + '/cookie';
+		return fetch(url).then(function(res) {
+			res.headers.delete('set-cookie');
+			expect(res.headers.get('set-cookie')).to.be.null;
+			expect(res.headers.getAll('set-cookie')).to.be.empty;
+		});
+	});
+
+	it('should send request with connection keep-alive if agent is provided', function() {
+		url = base + '/inspect';
+		opts = {
+			agent: new http.Agent({
+				keepAlive: true
+			})
+		};
+		return fetch(url, opts).then(function(res) {
+			return res.json();
+		}).then(function(res) {
+			expect(res.headers['connection']).to.equal('keep-alive');
+		});
+	});
+
+	it('should ignore unsupported attributes while reading headers', function() {
+		var FakeHeader = function() {};
+		// prototypes are ignored
+		FakeHeader.prototype.z = 'fake';
+
+		var res = new FakeHeader;
+		// valid
+		res.a = 'string';
+		res.b = ['1','2'];
+		res.c = '';
+		res.d = [];
+		// common mistakes, normalized
+		res.e = 1;
+		res.f = [1, 2];
+		// invalid, ignored
+		res.g = { a:1 };
+		res.h = undefined;
+		res.i = null;
+		res.j = NaN;
+		res.k = true;
+		res.l = false;
+		res.m = new Buffer('test');
+
+		var h1 = new Headers(res);
+
+		expect(h1._headers['a']).to.include('string');
+		expect(h1._headers['b']).to.include('1');
+		expect(h1._headers['b']).to.include('2');
+		expect(h1._headers['c']).to.include('');
+		expect(h1._headers['d']).to.be.undefined;
+
+		expect(h1._headers['e']).to.include('1');
+		expect(h1._headers['f']).to.include('1');
+		expect(h1._headers['f']).to.include('2');
+
+		expect(h1._headers['g']).to.be.undefined;
+		expect(h1._headers['h']).to.be.undefined;
+		expect(h1._headers['i']).to.be.undefined;
+		expect(h1._headers['j']).to.be.undefined;
+		expect(h1._headers['k']).to.be.undefined;
+		expect(h1._headers['l']).to.be.undefined;
+		expect(h1._headers['m']).to.be.undefined;
+
+		expect(h1._headers['z']).to.be.undefined;
+	});
+
+	it('should wrap headers', function() {
+		var h1 = new Headers({
+			a: '1'
+		});
+
+		var h2 = new Headers(h1);
+		h2.set('b', '1');
+
+		var h3 = new Headers(h2);
+		h3.append('a', '2');
+
+		expect(h1._headers['a']).to.include('1');
+		expect(h1._headers['a']).to.not.include('2');
+
+		expect(h2._headers['a']).to.include('1');
+		expect(h2._headers['a']).to.not.include('2');
+		expect(h2._headers['b']).to.include('1');
+
+		expect(h3._headers['a']).to.include('1');
+		expect(h3._headers['a']).to.include('2');
+		expect(h3._headers['b']).to.include('1');
+	});
+
+	it('should support fetch with Request instance', function() {
+		url = base + '/hello';
+		var req = new Request(url);
+		return fetch(req).then(function(res) {
+			expect(res.url).to.equal(url);
+			expect(res.ok).to.be.true;
+			expect(res.status).to.equal(200);
+		});
+	});
+
+	it('should support wrapping Request instance', function() {
+		url = base + '/hello';
+
+		var form = new FormData();
+		form.append('a', '1');
+
+		var r1 = new Request(url, {
+			method: 'POST'
+			, follow: 1
+			, body: form
+		});
+		var r2 = new Request(r1, {
+			follow: 2
+		});
+
+		expect(r2.url).to.equal(url);
+		expect(r2.method).to.equal('POST');
+		// note that we didn't clone the body
+		expect(r2.body).to.equal(form);
+		expect(r1.follow).to.equal(1);
+		expect(r2.follow).to.equal(2);
+		expect(r1.counter).to.equal(0);
+		expect(r2.counter).to.equal(0);
+	});
+
+	it('should support overwrite Request instance', function() {
+		url = base + '/inspect';
+		var req = new Request(url, {
+			method: 'POST'
+			, headers: {
+				a: '1'
+			}
+		});
+		return fetch(req, {
+			method: 'GET'
+			, headers: {
+				a: '2'
+			}
+		}).then(function(res) {
+			return res.json();
+		}).then(function(body) {
+			expect(body.method).to.equal('GET');
+			expect(body.headers.a).to.equal('2');
+		});
+	});
+
+	it('should support empty options in Response constructor', function() {
+		var body = resumer().queue('a=1').end();
+		body = body.pipe(new stream.PassThrough());
+		var res = new Response(body);
+		return res.text().then(function(result) {
+			expect(result).to.equal('a=1');
+		});
+	});
+
+	it('should support parsing headers in Response constructor', function() {
+		var res = new Response(null, {
+			headers: {
+				a: '1'
+			}
+		});
+		expect(res.headers.get('a')).to.equal('1');
+	});
+
+	it('should support text() method in Response constructor', function() {
+		var res = new Response('a=1');
+		return res.text().then(function(result) {
+			expect(result).to.equal('a=1');
+		});
+	});
+
+	it('should support json() method in Response constructor', function() {
+		var res = new Response('{"a":1}');
+		return res.json().then(function(result) {
+			expect(result.a).to.equal(1);
+		});
+	});
+
+	it('should support buffer() method in Response constructor', function() {
+		var res = new Response('a=1');
+		return res.buffer().then(function(result) {
+			expect(result.toString()).to.equal('a=1');
+		});
+	});
+
+	it('should support clone() method in Response constructor', function() {
+		var body = resumer().queue('a=1').end();
+		body = body.pipe(new stream.PassThrough());
+		var res = new Response(body, {
+			headers: {
+				a: '1'
+			}
+			, url: base
+			, status: 346
+			, statusText: 'production'
+		});
+		var cl = res.clone();
+		expect(cl.headers.get('a')).to.equal('1');
+		expect(cl.url).to.equal(base);
+		expect(cl.status).to.equal(346);
+		expect(cl.statusText).to.equal('production');
+		expect(cl.ok).to.be.false;
+		// clone body shouldn't be the same body
+		expect(cl.body).to.not.equal(body);
+		return cl.text().then(function(result) {
+			expect(result).to.equal('a=1');
+		});
+	});
+
+	it('should support stream as body in Response constructor', function() {
+		var body = resumer().queue('a=1').end();
+		body = body.pipe(new stream.PassThrough());
+		var res = new Response(body);
+		return res.text().then(function(result) {
+			expect(result).to.equal('a=1');
+		});
+	});
+
+	it('should support string as body in Response constructor', function() {
+		var res = new Response('a=1');
+		return res.text().then(function(result) {
+			expect(result).to.equal('a=1');
+		});
+	});
+
+	it('should support buffer as body in Response constructor', function() {
+		var res = new Response(new Buffer('a=1'));
+		return res.text().then(function(result) {
+			expect(result).to.equal('a=1');
+		});
+	});
+
+	it('should default to 200 as status code', function() {
+		var res = new Response(null);
+		expect(res.status).to.equal(200);
+	});
+
+	it('should support parsing headers in Request constructor', function() {
+		url = base;
+		var req = new Request(url, {
+			headers: {
+				a: '1'
+			}
+		});
+		expect(req.url).to.equal(url);
+		expect(req.headers.get('a')).to.equal('1');
+	});
+
+	it('should support text() method in Request constructor', function() {
+		url = base;
+		var req = new Request(url, {
+			body: 'a=1'
+		});
+		expect(req.url).to.equal(url);
+		return req.text().then(function(result) {
+			expect(result).to.equal('a=1');
+		});
+	});
+
+	it('should support json() method in Request constructor', function() {
+		url = base;
+		var req = new Request(url, {
+			body: '{"a":1}'
+		});
+		expect(req.url).to.equal(url);
+		return req.json().then(function(result) {
+			expect(result.a).to.equal(1);
+		});
+	});
+
+	it('should support buffer() method in Request constructor', function() {
+		url = base;
+		var req = new Request(url, {
+			body: 'a=1'
+		});
+		expect(req.url).to.equal(url);
+		return req.buffer().then(function(result) {
+			expect(result.toString()).to.equal('a=1');
+		});
+	});
+
+	it('should support arbitrary url in Request constructor', function() {
+		url = 'anything';
+		var req = new Request(url);
+		expect(req.url).to.equal('anything');
+	});
+
+	it('should support clone() method in Request constructor', function() {
+		url = base;
+		var body = resumer().queue('a=1').end();
+		body = body.pipe(new stream.PassThrough());
+		var agent = new http.Agent();
+		var req = new Request(url, {
+			body: body
+			, method: 'POST'
+			, redirect: 'manual'
+			, headers: {
+				b: '2'
+			}
+			, follow: 3
+			, compress: false
+			, agent: agent
+		});
+		var cl = req.clone();
+		expect(cl.url).to.equal(url);
+		expect(cl.method).to.equal('POST');
+		expect(cl.redirect).to.equal('manual');
+		expect(cl.headers.get('b')).to.equal('2');
+		expect(cl.follow).to.equal(3);
+		expect(cl.compress).to.equal(false);
+		expect(cl.method).to.equal('POST');
+		expect(cl.counter).to.equal(0);
+		expect(cl.agent).to.equal(agent);
+		// clone body shouldn't be the same body
+		expect(cl.body).to.not.equal(body);
+		return fetch.Promise.all([cl.text(), req.text()]).then(function(results) {
+			expect(results[0]).to.equal('a=1');
+			expect(results[1]).to.equal('a=1');
+		});
+	});
+
+	it('should support text(), json() and buffer() method in Body constructor', function() {
+		var body = new Body('a=1');
+		expect(body).to.have.property('text');
+		expect(body).to.have.property('json');
+		expect(body).to.have.property('buffer');
+	});
+
+	it('should create custom FetchError', function() {
+		var systemError = new Error('system');
+		systemError.code = 'ESOMEERROR';
+
+		var err = new FetchError('test message', 'test-error', systemError);
+		expect(err).to.be.an.instanceof(Error);
+		expect(err).to.be.an.instanceof(FetchError);
+		expect(err.name).to.equal('FetchError');
+		expect(err.message).to.equal('test message');
+		expect(err.type).to.equal('test-error');
+		expect(err.code).to.equal('ESOMEERROR');
+		expect(err.errno).to.equal('ESOMEERROR');
+	});
+
+	it('should support https request', function() {
+		this.timeout(5000);
+		url = 'https://github.com/';
+		opts = {
+			method: 'HEAD'
+		};
+		return fetch(url, opts).then(function(res) {
+			expect(res.status).to.equal(200);
+			expect(res.ok).to.be.true;
+		});
+	});
+
+});
-- 
cgit v1.2.3