diff options
author | Florian Dold <florian.dold@gmail.com> | 2017-05-03 15:35:00 +0200 |
---|---|---|
committer | Florian Dold <florian.dold@gmail.com> | 2017-05-03 15:35:00 +0200 |
commit | de98e0b232509d5f40c135d540a70e415272ff85 (patch) | |
tree | a79222a5b58484ab3b80d18efcaaa7ccc4769b33 /node_modules/node-fetch/lib | |
parent | e0c9d480a73fa629c1e4a47d3e721f1d2d345406 (diff) |
node_modules
Diffstat (limited to 'node_modules/node-fetch/lib')
-rw-r--r-- | node_modules/node-fetch/lib/body.js | 260 | ||||
-rw-r--r-- | node_modules/node-fetch/lib/fetch-error.js | 34 | ||||
-rw-r--r-- | node_modules/node-fetch/lib/headers.js | 141 | ||||
-rw-r--r-- | node_modules/node-fetch/lib/request.js | 75 | ||||
-rw-r--r-- | node_modules/node-fetch/lib/response.js | 50 |
5 files changed, 560 insertions, 0 deletions
diff --git a/node_modules/node-fetch/lib/body.js b/node_modules/node-fetch/lib/body.js new file mode 100644 index 000000000..e7bbe1dac --- /dev/null +++ b/node_modules/node-fetch/lib/body.js @@ -0,0 +1,260 @@ + +/** + * 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() { + + // for 204 No Content response, buffer will be empty, parsing it will throw error + if (this.status === 204) { + return Body.Promise.resolve({}); + } + + return this._decode().then(function(buffer) { + return JSON.parse(buffer.toString()); + }); + +}; + +/** + * 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/node_modules/node-fetch/lib/fetch-error.js b/node_modules/node-fetch/lib/fetch-error.js new file mode 100644 index 000000000..7cabfb3ce --- /dev/null +++ b/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/node_modules/node-fetch/lib/headers.js b/node_modules/node-fetch/lib/headers.js new file mode 100644 index 000000000..fd7a14eaa --- /dev/null +++ b/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 (headers[prop] instanceof Array) { + 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/node_modules/node-fetch/lib/request.js b/node_modules/node-fetch/lib/request.js new file mode 100644 index 000000000..1a29c29c6 --- /dev/null +++ b/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/node_modules/node-fetch/lib/response.js b/node_modules/node-fetch/lib/response.js new file mode 100644 index 000000000..f96aa85e8 --- /dev/null +++ b/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 + }); +}; |