Socket
Socket
Sign inDemoInstall

node-fetch

Package Overview
Dependencies
4
Maintainers
1
Versions
96
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.3.3 to 1.4.0

lib/body.js

12

CHANGELOG.md

@@ -8,4 +8,14 @@

## v1.3.3 (master)
## v1.4.0 (master)
- 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

@@ -12,0 +22,0 @@ - Fix: handle body stream error, for cases such as incorrect `Content-Encoding` header

@@ -15,2 +15,3 @@

var Body = require('./lib/body');
var Response = require('./lib/response');

@@ -20,3 +21,6 @@ var Headers = require('./lib/headers');

// commonjs
module.exports = Fetch;
// es6 default export compatibility
module.exports.default = module.exports;

@@ -41,3 +45,3 @@ /**

Response.Promise = Fetch.Promise;
Body.Promise = Fetch.Promise;

@@ -92,2 +96,5 @@ var self = this;

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') {
headers.set('content-length', options.body.getLengthSync().toString());
// this is only necessary for older nodejs releases (before iojs merge)

@@ -172,2 +179,3 @@ } else if (options.body === undefined || options.body === null) {

, status: res.statusCode
, statusText: res.statusMessage
, headers: headers

@@ -174,0 +182,0 @@ , size: options.size

@@ -1,2 +0,1 @@

/**

@@ -73,2 +72,17 @@ * headers.js

/**
* 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

@@ -75,0 +89,0 @@ *

25

lib/request.js

@@ -1,2 +0,1 @@

/**

@@ -10,2 +9,3 @@ * request.js

var Headers = require('./headers');
var Body = require('./body');

@@ -48,3 +48,2 @@ module.exports = Request;

this.headers = new Headers(init.headers || input.headers || {});
this.body = init.body || input.body;
this.url = url;

@@ -56,10 +55,13 @@

input.follow : 20;
this.counter = init.counter || input.follow || 0;
this.timeout = init.timeout || input.timeout || 0;
this.compress = init.compress !== undefined ?
init.compress : input.compress !== undefined ?
input.compress : true;
this.size = init.size || input.size || 0;
this.agent = init.agent || input.agent;
this.counter = init.counter || input.counter || input.follow || 0;
this.agent = init.agent || input.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

@@ -72,1 +74,12 @@ this.protocol = url_parsed.protocol;

}
Request.prototype = Object.create(Body.prototype);
/**
* Clone this request
*
* @return Request
*/
Request.prototype.clone = function() {
return new Request(this);
};

@@ -1,2 +0,1 @@

/**

@@ -9,4 +8,4 @@ * response.js

var http = require('http');
var convert = require('encoding').convert;
var Headers = require('./headers');
var Body = require('./body');

@@ -28,161 +27,25 @@ module.exports = Response;

this.status = opts.status;
this.statusText = http.STATUS_CODES[this.status];
this.statusText = opts.statusText || http.STATUS_CODES[this.status];
this.headers = new Headers(opts.headers);
this.body = body;
this.bodyUsed = false;
this.size = opts.size;
this.ok = this.status >= 200 && this.status < 300;
this.timeout = opts.timeout;
}
Body.call(this, body, opts);
/**
* Decode response as json
*
* @return Promise
*/
Response.prototype.json = function() {
return this._decode().then(function(text) {
return JSON.parse(text);
});
}
/**
* Decode response as text
*
* @return Promise
*/
Response.prototype.text = function() {
Response.prototype = Object.create(Body.prototype);
return this._decode();
}
/**
* Decode buffers into utf-8 string
* Clone this response
*
* @return Promise
* @return Response
*/
Response.prototype._decode = function() {
var self = this;
if (this.bodyUsed) {
return Response.Promise.reject(new Error('body used already for: ' + this.url));
}
this.bodyUsed = true;
this._bytes = 0;
this._abort = false;
this._raw = [];
return new Response.Promise(function(resolve, reject) {
var resTimeout;
// allow timeout on slow response body
if (self.timeout) {
resTimeout = setTimeout(function() {
self._abort = true;
reject(new Error('response timeout at ' + self.url + ' over limit: ' + self.timeout));
}, self.timeout);
}
// handle stream error, such as incorrect content-encoding
self.body.on('error', function(err) {
reject(new Error('invalid response body at: ' + self.url + ' reason: ' + err.message));
});
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 Error('content size at ' + self.url + ' over limit: ' + self.size));
return;
}
self._bytes += chunk.length;
self._raw.push(chunk);
});
self.body.on('end', function() {
if (self._abort) {
return;
}
clearTimeout(resTimeout);
resolve(self._convert());
});
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
});
};
/**
* 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
*/
Response.prototype._convert = function(encoding) {
encoding = encoding || 'utf-8';
var charset = 'utf-8';
var res, str;
// header
if (this.headers.has('content-type')) {
res = /charset=([^;]*)/i.exec(this.headers.get('content-type'));
}
// no charset in content type, peek at response body
if (!res && this._raw.length > 0) {
str = this._raw[0].toString().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 utf-8 string
return convert(
Buffer.concat(this._raw)
, encoding
, charset
).toString();
}
// expose Promise
Response.Promise = global.Promise;
{
"name": "node-fetch",
"version": "1.3.3",
"version": "1.4.0",
"description": "A light-weight module that brings window.fetch to node.js and io.js",

@@ -27,16 +27,17 @@ "main": "index.js",

"devDependencies": {
"bluebird": "^2.9.1",
"chai": "^1.10.0",
"chai-as-promised": "^4.1.1",
"bluebird": "^3.3.4",
"chai": "^3.5.0",
"chai-as-promised": "^5.2.0",
"coveralls": "^2.11.2",
"form-data": "^1.0.0-rc1",
"istanbul": "^0.3.5",
"istanbul": "^0.4.2",
"mocha": "^2.1.0",
"parted": "^0.1.1",
"promise": "^6.1.0",
"promise": "^7.1.1",
"resumer": "0.0.0"
},
"dependencies": {
"encoding": "^0.1.11"
"encoding": "^0.1.11",
"is-stream": "^1.0.1"
}
}

@@ -44,2 +44,8 @@

if (p === '/options') {
res.statusCode = 200;
res.setHeader('Allow', 'GET, HEAD, OPTIONS');
res.end('hello world');
}
if (p === '/html') {

@@ -172,2 +178,25 @@ res.statusCode = 200;

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') {

@@ -174,0 +203,0 @@ res.statusCode = 301;

@@ -13,2 +13,3 @@

var FormData = require('form-data');
var http = require('http');

@@ -22,2 +23,3 @@ var TestServer = require('./server');

var Request = require('../lib/request.js');
var Body = require('../lib/body.js');
// test with native promise on node 0.11, and bluebird for node 0.10

@@ -528,2 +530,3 @@ fetch.Promise = fetch.Promise || bluebird;

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');

@@ -551,2 +554,3 @@ });

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');

@@ -610,2 +614,15 @@ expect(res.body).to.equal('a=1');

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() {

@@ -717,2 +734,26 @@ url = base + '/plain';

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) {

@@ -734,2 +775,58 @@ url = base + '/hello';

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([r1.text(), res.json()]).then(function(results) {
expect(results[0]).to.equal('{"name":"value"}');
expect(results[1]).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() {

@@ -745,2 +842,24 @@ url = base + '/cookie';

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() {

@@ -812,3 +931,2 @@ url = base + '/cookie';

expect(h1._headers['a']).to.not.include('2');
expect(h1._headers['b']).to.not.include('1');

@@ -880,5 +998,3 @@ expect(h2._headers['a']).to.include('1');

it('should support parsing headers in Response constructor', function() {
var body = resumer().queue('a=1').end();
body = body.pipe(new stream.PassThrough());
var res = new Response(body, {
var res = new Response(null, {
headers: {

@@ -889,2 +1005,6 @@ 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) {

@@ -895,2 +1015,52 @@ 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 clone() method in Response constructor', function() {
var res = new Response('a=1', {
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;
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 support parsing headers in Request constructor', function() {

@@ -907,2 +1077,58 @@ url = base;

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 clone() method in Request constructor', function() {
url = base;
var agent = new http.Agent();
var req = new Request(url, {
body: 'a=1'
, method: 'POST'
, 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.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(3);
expect(cl.agent).to.equal(agent);
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() and json() method in Body constructor', function() {
var body = new Body('a=1');
expect(body).to.have.property('text');
expect(body).to.have.property('json');
});

it('should support https request', function() {

@@ -909,0 +1135,0 @@ this.timeout(5000);

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Packages

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc