diff options
Diffstat (limited to 'hash_script/node_modules/node-fetch')
18 files changed, 4677 insertions, 0 deletions
| 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; +		}); +	}); + +}); | 
