Comparing version 0.9.1 to 0.9.2
@@ -1,77 +0,81 @@ | ||
var valid_keys = ['domain', 'path', 'expires', 'httponly', 'secure']; | ||
// | ||
// Simple cookie handling implementation based on the standard RFC 6265. | ||
// This module just has two functionalities: | ||
// - Parse a set-cookie-header as a key value object | ||
// - Write a cookie-string from a key value object | ||
// All cookie attributes are ignored. | ||
// | ||
// modified version of the parse function from the cookie lib by jshttp: | ||
// https://github.com/jshttp/cookie/blob/master/index.js | ||
// RegExps | ||
function parse(str, opt) { | ||
var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/; | ||
var EXCLUDED_CHARS = /[\x00-\x1F\x7F\x3B\x3B\s\"\,\\"%]/g; | ||
var TRAILING_SEMICOLON = /\x3B+$/; | ||
var SEP_SEMICOLON = /\s*\x3B\s*/; | ||
var obj = {}, | ||
res = {}, | ||
pairs = str.split(/; */), // matches no whitespace too | ||
dec = decodeURIComponent; | ||
// Constants | ||
pairs.forEach(function(pair) { | ||
var index = pair.indexOf('='); | ||
var KEY_INDEX = 1; // index of key from COOKIE_PAIR match | ||
var VALUE_INDEX = 3; // index of value from COOKIE_PAIR match | ||
// skip things that don't look like key=value | ||
if (index < 0) | ||
return; | ||
// Convenience functions | ||
var key = pair.substr(0, index).trim(), | ||
val = pair.substr(++index, pair.length).trim(); | ||
// Returns a copy str trimmed and without trainling semicolon. | ||
function cleanCookieString(str) { | ||
return str.trim().replace(/\x3B+$/, ''); | ||
} | ||
// quoted values | ||
if ('"' == val[0]) { | ||
val = val.slice(1, -1); | ||
} | ||
function getFirstPair(str) { | ||
var index = str.indexOf('\x3B'); | ||
return index === -1 ? str : str.substr(0, index); | ||
} | ||
// only assign once | ||
if (undefined == obj[key]) { | ||
try { | ||
obj[key] = dec(val); | ||
} catch (e) { | ||
obj[key] = val; | ||
} | ||
} | ||
}); | ||
// Private functions | ||
// if key is part of valid_keys, set it, otherwise set it as key=[key] and value=[val] | ||
for (var key in obj) { | ||
if (valid_keys.indexOf(key.toLowerCase()) != -1) { | ||
res[key] = obj[key]; | ||
} else { | ||
res.key = key; | ||
res.value = obj[key]; | ||
} | ||
} | ||
// Returns a encoded copy of str based on RFC6265 S4.1.1. | ||
function encodeCookieComponent(str) { | ||
return str.toString().replace(EXCLUDED_CHARS, encodeURIComponent); | ||
} | ||
return res; | ||
}; | ||
// Parses a set-cookie-string based on the standard definded in RFC6265 S4.1.1. | ||
function parseSetCookieString(str) { | ||
str = cleanCookieString(str); | ||
str = getFirstPair(str); | ||
function to_hash(arr, url) { | ||
var res = {}; | ||
var res = COOKIE_PAIR.exec(str); | ||
arr.forEach(function(obj) { | ||
// if (!obj.path || (url.indexOf(obj.path) !== -1)) { | ||
res[obj.key] = obj.value; | ||
// } | ||
}) | ||
return { | ||
name: decodeURIComponent(res[KEY_INDEX]), | ||
value: decodeURIComponent(res[VALUE_INDEX]) | ||
}; | ||
} | ||
return res; | ||
// Parses a set-cookie-header and returns a key/value object. Each key | ||
// represent a name of a cookie. | ||
function parseSetCookieHeader(header) { | ||
header = (header instanceof Array) ? header : [header]; | ||
return header.reduce(function(res, str) { | ||
var cookie = parseSetCookieString(str); | ||
res[cookie.name] = cookie.value; | ||
return res; | ||
}, {}); | ||
} | ||
// returns a key/val object from an array of | ||
exports.read = function(header) { | ||
var arr = (header instanceof Array) ? header : [header]; | ||
return to_hash(arr.map(function(c) { return parse(c); })); | ||
// Writes a set-cookie-string based on the standard definded in RFC6265 S4.1.1. | ||
function writeCookieString(obj) { | ||
return Object.keys(obj).reduce(function(str, name) { | ||
var encodedName = encodeCookieComponent(name); | ||
var encodedValue = encodeCookieComponent(obj[name]); | ||
str += (str ? "; " : "") + encodedName + '=' + encodedValue; | ||
return str; | ||
}, ""); | ||
} | ||
exports.write = function(obj) { | ||
var res, enc = encodeURIComponent; | ||
// Module interface | ||
res = Object.keys(obj).map(function(key) { | ||
return enc(key) + '=' + enc(obj[key]); | ||
}) | ||
// returns a key/val object from an array of cookie strings | ||
exports.read = parseSetCookieHeader; | ||
return res.join('; '); | ||
}; | ||
// writes a cookie string header | ||
exports.write = writeCookieString; |
@@ -398,3 +398,3 @@ ////////////////////////////////////////// | ||
if (config.follow_set_cookies) | ||
if (config.follow_set_cookies && resp.cookies) | ||
config.headers['Cookie'] = cookies.write(resp.cookies); | ||
@@ -401,0 +401,0 @@ |
@@ -16,3 +16,3 @@ // based on the qs module, but handles null objects as expected | ||
} else { | ||
throw new TypeError('Invalid object received:' + obj); | ||
throw new TypeError('Cannot build a querystring out of: ' + obj); | ||
} | ||
@@ -19,0 +19,0 @@ }; |
{ | ||
"name": "needle", | ||
"version": "0.9.1", | ||
"version": "0.9.2", | ||
"description": "The leanest and most handsome HTTP client in the Nodelands.", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
@@ -1,75 +0,204 @@ | ||
var needle = require('../'), | ||
sinon = require('sinon'), | ||
should = require('should'), | ||
http = require('http'), | ||
cookies = require('../lib/cookies'); | ||
var needle = require('../'), | ||
sinon = require('sinon'), | ||
http = require('http'), | ||
should = require('should'), | ||
assert = require('assert'); | ||
var WEIRD_COOKIE_NAME = 'wc', | ||
BASE64_COOKIE_NAME = 'bc', | ||
FORBIDDEN_COOKIE_NAME = 'fc', | ||
NUMBER_COOKIE_NAME = 'nc', | ||
WEIRD_COOKIE_VALUE = '!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[' + | ||
']^_`abcdefghijklmnopqrstuvwxyz{|}~', | ||
BASE64_COOKIE_VALUE = 'Y29va2llCg==', | ||
FORBIDDEN_COOKIE_VALUE = ' ;"\\,', | ||
NUMBER_COOKIE_VALUE = 12354342, | ||
WEIRD_COOKIE = 'wc=!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~', | ||
BASE64_COOKIE = 'bc=Y29va2llCg==', | ||
FORBIDDEN_COOKIE = 'fc=%20%3B%22%5C%2C', | ||
NUMBER_COOKIE = 'nc=12354342', | ||
COOKIE_HEADER = WEIRD_COOKIE + '; ' + BASE64_COOKIE + '; ' + | ||
FORBIDDEN_COOKIE + '; ' + NUMBER_COOKIE, | ||
TEST_HOST = 'localhost'; | ||
NO_COOKIES_TEST_PORT = 11112, ALL_COOKIES_TEST_PORT = 11113; | ||
function decode(str) { | ||
return decodeURIComponent(str); | ||
} | ||
function encode(str) { | ||
str = str.toString().replace(/[\x00-\x1F\x7F]/g, encodeURIComponent); | ||
return str.replace(/[\s\"\,;\\%]/g, encodeURIComponent); | ||
} | ||
describe('cookies', function() { | ||
var last_req; | ||
var headers, server, opts; | ||
var server = http.createServer(function(req, res) { | ||
last_req = req; | ||
res.end('Thanks.'); | ||
}) | ||
before(function() { | ||
setCookieHeader = [ | ||
WEIRD_COOKIE_NAME + '=' + encode(WEIRD_COOKIE_VALUE) + ';', | ||
BASE64_COOKIE_NAME + '=' + encode(BASE64_COOKIE_VALUE) + ';', | ||
FORBIDDEN_COOKIE_NAME + '=' + encode(FORBIDDEN_COOKIE_VALUE) + ';', | ||
NUMBER_COOKIE_NAME + '=' + encode(NUMBER_COOKIE_VALUE) + ';' | ||
]; | ||
}); | ||
before(function(done) { | ||
server.listen(function() { | ||
port = this.address().port; | ||
done(); | ||
}); | ||
}) | ||
serverAllCookies = http.createServer(function(req, res) { | ||
res.setHeader('Content-Type', 'text/html'); | ||
res.setHeader('Set-Cookie', setCookieHeader); | ||
res.end('200'); | ||
}).listen(ALL_COOKIES_TEST_PORT, TEST_HOST, done); | ||
}); | ||
after(function(done) { | ||
server.close(done) | ||
}) | ||
serverAllCookies.close(done); | ||
}); | ||
describe('with default options', function() { | ||
it('no cookie header is set on request', function(done) { | ||
needle.get( | ||
TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(err, response) { | ||
assert(!response.req._headers.cookie); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
it('no cookie header is set on request', function() { | ||
describe('if response does not contain cookies', function() { | ||
before(function(done) { | ||
serverNoCookies = http.createServer(function(req, res) { | ||
res.setHeader('Content-Type', 'text/html'); | ||
res.end('200'); | ||
}).listen(NO_COOKIES_TEST_PORT, TEST_HOST, done); | ||
}); | ||
}) | ||
it('response.cookies is undefined', function(done) { | ||
needle.get( | ||
TEST_HOST + ':' + NO_COOKIES_TEST_PORT, function(error, response) { | ||
assert(!response.cookies); | ||
done(); | ||
}); | ||
}); | ||
}) | ||
after(function(done) { | ||
serverNoCookies.close(done); | ||
}); | ||
}); | ||
describe('if response does not contain cookies', function() { | ||
describe('if response contains cookies', function() { | ||
it('puts them on resp.cookies', function(done) { | ||
needle.get( | ||
TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) { | ||
response.should.have.property('cookies'); | ||
done(); | ||
}); | ||
}); | ||
it('resp.cookies is undefined', function() { | ||
it('parses them as a object', function(done) { | ||
needle.get( | ||
TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) { | ||
response.cookies.should.be.an.instanceOf(Object) | ||
.and.have.property(WEIRD_COOKIE_NAME); | ||
response.cookies.should.have.property(BASE64_COOKIE_NAME); | ||
response.cookies.should.have.property(FORBIDDEN_COOKIE_NAME); | ||
response.cookies.should.have.property(NUMBER_COOKIE_NAME); | ||
done(); | ||
}); | ||
}); | ||
}) | ||
it('must decode it', function(done) { | ||
needle.get( | ||
TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) { | ||
response.cookies.wc.should.be.eql(WEIRD_COOKIE_VALUE); | ||
response.cookies.bc.should.be.eql(BASE64_COOKIE_VALUE); | ||
response.cookies.fc.should.be.eql(FORBIDDEN_COOKIE_VALUE); | ||
response.cookies.nc.should.be.eql(NUMBER_COOKIE_VALUE.toString()); | ||
done(); | ||
}); | ||
}); | ||
}) | ||
describe('and response is a redirect', function() { | ||
describe('and follow_set_cookies is false', function() { | ||
it('no cookie header set on redirection request', function() {}); | ||
}); | ||
describe('and follow_set_cookies is true', function() {}); | ||
}); | ||
}); | ||
describe('if response contains cookies', function() { | ||
describe('if resquest contains cookie header', function() { | ||
var opts = { | ||
cookies: {} | ||
}; | ||
it('parses them', function() { | ||
before(function() { | ||
opts.cookies[WEIRD_COOKIE_NAME] = WEIRD_COOKIE_VALUE; | ||
opts.cookies[BASE64_COOKIE_NAME] = BASE64_COOKIE_VALUE; | ||
opts.cookies[FORBIDDEN_COOKIE_NAME] = FORBIDDEN_COOKIE_VALUE; | ||
opts.cookies[NUMBER_COOKIE_NAME] = NUMBER_COOKIE_VALUE; | ||
}); | ||
}) | ||
it('must be a valid cookie string', function(done) { | ||
var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/; | ||
it('puts them on resp.cookies', function() { | ||
needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, opts, function(error, response) { | ||
var cookieString = response.req._headers.cookie; | ||
}) | ||
cookieString.should.be.type('string'); | ||
describe('and response is a redirect', function() { | ||
cookieString.split(/\s*;\s*/).forEach(function(pair) { | ||
COOKIE_PAIR.test(pair).should.be.exactly(true); | ||
}); | ||
describe('and follow_set_cookies is false', function() { | ||
cookieString.should.be.exactly(COOKIE_HEADER); | ||
}) | ||
done(); | ||
}); | ||
}); | ||
describe('and follow_set_cookies is true', function() { | ||
}) | ||
it('dont have to encode allowed characters', function(done) { | ||
var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/, | ||
KEY_INDEX = 1, | ||
VALUE_INEX = 3; | ||
}) | ||
needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, opts, function(error, response) { | ||
var cookieObj = {}, | ||
cookieString = response.req._headers.cookie; | ||
}) | ||
cookieString.split(/\s*;\s*/).forEach(function(str) { | ||
var pair = COOKIE_PAIR.exec(str); | ||
cookieObj[pair[KEY_INDEX]] = pair[VALUE_INEX]; | ||
}); | ||
describe('when passing an object on options.cookies', function() { | ||
cookieObj[WEIRD_COOKIE_NAME].should.be.exactly(WEIRD_COOKIE_VALUE); | ||
cookieObj[BASE64_COOKIE_NAME].should.be.exactly(BASE64_COOKIE_VALUE); | ||
done(); | ||
}); | ||
}); | ||
it('sets the cookies', function() { | ||
it('must encode forbidden characters', function(done) { | ||
var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/, | ||
KEY_INDEX = 1, | ||
VALUE_INEX = 3; | ||
}) | ||
needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, opts, function(error, response) { | ||
var cookieObj = {}, | ||
cookieString = response.req._headers.cookie; | ||
}) | ||
cookieString.split(/\s*;\s*/).forEach(function(str) { | ||
var pair = COOKIE_PAIR.exec(str); | ||
cookieObj[pair[KEY_INDEX]] = pair[VALUE_INEX]; | ||
}); | ||
}); | ||
cookieObj[FORBIDDEN_COOKIE_NAME].should.not.be.eql( | ||
FORBIDDEN_COOKIE_VALUE); | ||
cookieObj[FORBIDDEN_COOKIE_NAME].should.be.exactly( | ||
encode(FORBIDDEN_COOKIE_VALUE)); | ||
cookieObj[FORBIDDEN_COOKIE_NAME].should.be.exactly( | ||
encodeURIComponent(FORBIDDEN_COOKIE_VALUE)); | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -9,7 +9,6 @@ var should = require('should'), | ||
var url; | ||
this.timeout(5000); | ||
describe('test A', function() { | ||
this.timeout(5000); | ||
before(function() { | ||
@@ -44,3 +43,3 @@ url = 'http://www.huanqiukexue.com/html/newgc/2014/1215/25011.html'; | ||
}) | ||
}) | ||
@@ -47,0 +46,0 @@ |
@@ -110,27 +110,27 @@ var needle = require('../'), | ||
it('emits end event once, with error', function(done) { | ||
var called = 0, | ||
var callcount = 0, | ||
stream = needle.get(url); | ||
stream.on('end', function(err) { | ||
called++; | ||
callcount++; | ||
}) | ||
setTimeout(function() { | ||
called.should.equal(1); | ||
callcount.should.equal(1); | ||
done(); | ||
}, 50) | ||
}, 200) | ||
}) | ||
it('error should be ENOTFOUND or EADDRINFO', function(done){ | ||
var error, | ||
it('error should be ENOTFOUND or EADDRINFO', function(done) { | ||
var errorific, | ||
stream = needle.get(url); | ||
stream.on('end', function(err) { | ||
error = err; | ||
errorific = err; | ||
}) | ||
setTimeout(function() { | ||
error.code.should.match(/ENOTFOUND|EADDRINFO/) | ||
errorific.code.should.match(/ENOTFOUND|EADDRINFO/) | ||
done(); | ||
}, 50) | ||
}, 200) | ||
}) | ||
@@ -137,0 +137,0 @@ |
@@ -64,3 +64,4 @@ var helpers = require('./helpers'), | ||
var opts, // each test will modify this | ||
url = protocol + '://localhost:' + ports[protocol] + '/hello'; | ||
host = '127.0.0.1', | ||
url = protocol + '://' + host + ':' + ports[protocol] + '/hello'; | ||
@@ -165,3 +166,3 @@ function send_request(opts, cb) { | ||
before(function() { | ||
location = url.replace('localhost', hostname); | ||
location = url.replace(host, hostname); | ||
}) | ||
@@ -173,3 +174,3 @@ it('follows redirect', followed_same_protocol); | ||
before(function() { | ||
location = url.replace('localhost', hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]); | ||
location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]); | ||
}) | ||
@@ -283,3 +284,3 @@ it('follows redirect', followed_other_protocol); | ||
send_request(opts, function(err, resp) { | ||
spies.http.args[0][0].headers['Referer'].should.eql("http://localhost:8888/hello"); | ||
spies.http.args[0][0].headers['Referer'].should.eql("http://" + host + ":8888/hello"); | ||
// spies.http.args[0][3].should.eql({ foo: 'bar'}); | ||
@@ -330,3 +331,3 @@ done(); | ||
before(function() { | ||
location = url.replace('localhost', hostname); | ||
location = url.replace(host, hostname); | ||
}) | ||
@@ -347,3 +348,3 @@ it('follows redirect', followed_same_protocol); | ||
before(function() { | ||
location = url.replace('localhost', hostname); | ||
location = url.replace(host, hostname); | ||
}) | ||
@@ -365,3 +366,3 @@ | ||
before(function() { | ||
location = url.replace('localhost', hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]); | ||
location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]); | ||
}) | ||
@@ -382,3 +383,3 @@ it('follows redirect', followed_other_protocol); | ||
before(function() { | ||
location = url.replace('localhost', hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]); | ||
location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]); | ||
}) | ||
@@ -385,0 +386,0 @@ it('does not follow redirect', not_followed); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
2599
119278