From d324474467727fa31c76c206acb027acd8925fa1 Mon Sep 17 00:00:00 2001 From: NateN1222 Date: Sun, 3 Sep 2017 17:38:26 -0500 Subject: Implemented a default whitelist --- hash_script/node_modules/node-fetch/lib/body.js | 261 ++++ .../node_modules/node-fetch/lib/fetch-error.js | 34 + hash_script/node_modules/node-fetch/lib/headers.js | 141 ++ hash_script/node_modules/node-fetch/lib/index.js | 1416 ++++++++++++++++++++ hash_script/node_modules/node-fetch/lib/request.js | 75 ++ .../node_modules/node-fetch/lib/response.js | 50 + 6 files changed, 1977 insertions(+) create mode 100644 hash_script/node_modules/node-fetch/lib/body.js create mode 100644 hash_script/node_modules/node-fetch/lib/fetch-error.js create mode 100644 hash_script/node_modules/node-fetch/lib/headers.js create mode 100644 hash_script/node_modules/node-fetch/lib/index.js create mode 100644 hash_script/node_modules/node-fetch/lib/request.js create mode 100644 hash_script/node_modules/node-fetch/lib/response.js (limited to 'hash_script/node_modules/node-fetch/lib') 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 = / 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 = /= 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> + // 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 + 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 + }); +}; -- cgit v1.2.3