Skip to content

Commit

Permalink
Merge pull request #102 from alessioalex/master
Browse files Browse the repository at this point in the history
Implemented cookies - closes issue 82: #82
  • Loading branch information
mikeal committed Nov 14, 2011
2 parents 8869b2e + 856a65c commit 43cd887
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 2 deletions.
43 changes: 43 additions & 0 deletions README.md
Expand Up @@ -112,6 +112,7 @@ The first argument can be either a url or an options object. The only required o
* `timeout` - Integer containing the number of milliseconds to wait for a request to respond before aborting the request
* `proxy` - An HTTP proxy to be used. Support proxy Auth with Basic Auth the same way it's supported with the `url` parameter by embedding the auth info in the uri.
* `strictSSL` - Set to `true` to require that SSL certificates be valid. Note: to use your own certificate authority, you need to specify an agent that was created with that ca as an option.
* `jar` - Set to `false` if you don't want cookies to be remembered for future use or define your custom cookie jar (see examples section)


The callback argument gets 3 arguments. The first is an error when applicable (usually from the http.Client option not the http.ClientRequest object). The second in an http.ClientResponse object. The third is the response body buffer.
Expand Down Expand Up @@ -163,6 +164,20 @@ Alias to normal request method for uniformity.
```javascript
request.get(url)
```
### request.cookie

Function that creates a new cookie.

```javascript
request.cookie('cookie_string_here')
```
### request.jar

Function that creates a new cookie jar.

```javascript
request.jar()
```


## Examples:
Expand Down Expand Up @@ -191,3 +206,31 @@ request.get(url)
}
)
```
Cookies are enabled by default (so they can be used in subsequent requests). To disable cookies set jar to false (either in defaults or in the options sent).

```javascript
var request = request.defaults({jar: false})
request('http://www.google.com', function () {
request('http://images.google.com')
})
```

If you to use a custom cookie jar (instead of letting request use its own global cookie jar) you do so by setting the jar default or by specifying it as an option:

```javascript
var j = request.jar()
var request = request.defaults({jar:j})
request('http://www.google.com', function () {
request('http://images.google.com')
})
```
OR

```javascript
var j = request.jar()
var cookie = request.cookie('your_cookie_here')
j.add(cookie)
request({url: 'http://www.google.com', jar: j}, function () {
request('http://images.google.com')
})
```
37 changes: 37 additions & 0 deletions main.js
Expand Up @@ -22,6 +22,9 @@ var http = require('http')
, mimetypes = require('./mimetypes')
, oauth = require('./oauth')
, uuid = require('./uuid')
, Cookie = require('./vendor/cookie')
, CookieJar = require('./vendor/cookie/jar')
, cookieJar = new CookieJar
;

try {
Expand Down Expand Up @@ -137,6 +140,19 @@ Request.prototype.request = function () {
setHost = true
}

if (self.jar === false) {
// disable cookies
var cookies = false;
self._disableCookies = true;
} else if (self.jar) {
// fetch cookie from the user defined cookie jar
var cookies = self.jar.get({ url: self.uri.href })
} else {
// fetch cookie from the global cookie jar
var cookies = cookieJar.get({ url: self.uri.href })
}
if (cookies) {self.headers.Cookie = cookies}

if (!self.uri.pathname) {self.uri.pathname = '/'}
if (!self.uri.port) {
if (self.uri.protocol == 'http:') {self.uri.port = 80}
Expand Down Expand Up @@ -406,6 +422,18 @@ Request.prototype.request = function () {
response.body = JSON.parse(response.body)
} catch (e) {}
}
if (response.statusCode == 200 && response.headers['set-cookie'] && (!self._disableCookies)) {
response.headers['set-cookie'].forEach(function(cookie) {
if (self.jar) {
// custom defined jar
self.jar.add(new Cookie(cookie));
} else {
// add to the global cookie jar if user don't define his own
cookieJar.add(new Cookie(cookie));
}
});
}

self.callback(null, response, response.body)
})
}
Expand Down Expand Up @@ -525,6 +553,8 @@ request.defaults = function (options) {
de.put = def(request.put)
de.head = def(request.head)
de.del = def(request.del)
de.cookie = def(request.cookie)
de.jar = def(request.jar)
return de
}

Expand Down Expand Up @@ -552,3 +582,10 @@ request.del = function (options, callback) {
options.method = 'DELETE'
return request(options, callback)
}
request.jar = function () {
return new CookieJar
}
request.cookie = function (str) {
if (typeof str !== 'string') throw new Error("The cookie function only accepts STRING as param")
return new Cookie(str)
}
29 changes: 29 additions & 0 deletions tests/test-cookie.js
@@ -0,0 +1,29 @@
var Cookie = require('../vendor/cookie')
, assert = require('assert');

var str = 'sid=s543qactge.wKE61E01Bs%2BKhzmxrwrnug; path=/; httpOnly; expires=Sat, 04 Dec 2010 23:27:28 GMT';
var cookie = new Cookie(str);

// test .toString()
assert.equal(cookie.toString(), str);

// test .path
assert.equal(cookie.path, '/');

// test .httpOnly
assert.equal(cookie.httpOnly, true);

// test .name
assert.equal(cookie.name, 'sid');

// test .value
assert.equal(cookie.value, 's543qactge.wKE61E01Bs%2BKhzmxrwrnug');

// test .expires
assert.equal(cookie.expires instanceof Date, true);

// test .path default
var cookie = new Cookie('foo=bar', { url: 'http://foo.com/bar' });
assert.equal(cookie.path, '/bar');

console.log('All tests passed');
90 changes: 90 additions & 0 deletions tests/test-cookiejar.js
@@ -0,0 +1,90 @@
var Cookie = require('../vendor/cookie')
, Jar = require('../vendor/cookie/jar')
, assert = require('assert');

function expires(ms) {
return new Date(Date.now() + ms).toUTCString();
}

// test .get() expiration
(function() {
var jar = new Jar;
var cookie = new Cookie('sid=1234; path=/; expires=' + expires(1000));
jar.add(cookie);
setTimeout(function(){
var cookies = jar.get({ url: 'http://foo.com/foo' });
assert.equal(cookies.length, 1);
assert.equal(cookies[0], cookie);
setTimeout(function(){
var cookies = jar.get({ url: 'http://foo.com/foo' });
assert.equal(cookies.length, 0);
}, 1000);
}, 5);
})();

// test .get() path support
(function() {
var jar = new Jar;
var a = new Cookie('sid=1234; path=/');
var b = new Cookie('sid=1111; path=/foo/bar');
var c = new Cookie('sid=2222; path=/');
jar.add(a);
jar.add(b);
jar.add(c);

// should remove the duplicates
assert.equal(jar.cookies.length, 2);

// same name, same path, latter prevails
var cookies = jar.get({ url: 'http://foo.com/' });
assert.equal(cookies.length, 1);
assert.equal(cookies[0], c);

// same name, diff path, path specifity prevails, latter prevails
var cookies = jar.get({ url: 'http://foo.com/foo/bar' });
assert.equal(cookies.length, 1);
assert.equal(cookies[0], b);

var jar = new Jar;
var a = new Cookie('sid=1111; path=/foo/bar');
var b = new Cookie('sid=1234; path=/');
jar.add(a);
jar.add(b);

var cookies = jar.get({ url: 'http://foo.com/foo/bar' });
assert.equal(cookies.length, 1);
assert.equal(cookies[0], a);

var cookies = jar.get({ url: 'http://foo.com/' });
assert.equal(cookies.length, 1);
assert.equal(cookies[0], b);

var jar = new Jar;
var a = new Cookie('sid=1111; path=/foo/bar');
var b = new Cookie('sid=3333; path=/foo/bar');
var c = new Cookie('pid=3333; path=/foo/bar');
var d = new Cookie('sid=2222; path=/foo/');
var e = new Cookie('sid=1234; path=/');
jar.add(a);
jar.add(b);
jar.add(c);
jar.add(d);
jar.add(e);

var cookies = jar.get({ url: 'http://foo.com/foo/bar' });
assert.equal(cookies.length, 2);
assert.equal(cookies[0], b);
assert.equal(cookies[1], c);

var cookies = jar.get({ url: 'http://foo.com/foo/' });
assert.equal(cookies.length, 1);
assert.equal(cookies[0], d);

var cookies = jar.get({ url: 'http://foo.com/' });
assert.equal(cookies.length, 1);
assert.equal(cookies[0], e);
})();

setTimeout(function() {
console.log('All tests passed');
}, 1200);
2 changes: 1 addition & 1 deletion tests/test-errors.js
Expand Up @@ -27,4 +27,4 @@ try {
assert.equal(e.message, 'Body attribute missing in multipart.')
}

console.log("All tests passed.")
console.log("All tests passed.")
2 changes: 1 addition & 1 deletion uuid.js
Expand Up @@ -16,4 +16,4 @@ module.exports = function () {
s[8] = s[13] = s[18] = s[23] = '-';

return s.join('');
}
}
57 changes: 57 additions & 0 deletions vendor/cookie/index.js
@@ -0,0 +1,57 @@
/*!
* Tobi - Cookie
* Copyright(c) 2010 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/

/**
* Module dependencies.
*/

var url = require('url');

/**
* Initialize a new `Cookie` with the given cookie `str` and `req`.
*
* @param {String} str
* @param {IncomingRequest} req
* @api private
*/

var Cookie = exports = module.exports = function Cookie(str, req) {
this.str = str;

// First key is the name
this.name = str.substr(0, str.indexOf('='));

// Map the key/val pairs
str.split(/ *; */).reduce(function(obj, pair){
pair = pair.split(/ *= */);
obj[pair[0]] = pair[1] || true;
return obj;
}, this);

// Assign value
this.value = this[this.name];

// Expires
this.expires = this.expires
? new Date(this.expires)
: Infinity;

// Default or trim path
this.path = this.path
? this.path.trim()
: url.parse(req.url).pathname;
};

/**
* Return the original cookie string.
*
* @return {String}
* @api public
*/

Cookie.prototype.toString = function(){
return this.str;
};
72 changes: 72 additions & 0 deletions vendor/cookie/jar.js
@@ -0,0 +1,72 @@
/*!
* Tobi - CookieJar
* Copyright(c) 2010 LearnBoost <dev@learnboost.com>
* MIT Licensed
*/

/**
* Module dependencies.
*/

var url = require('url');

/**
* Initialize a new `CookieJar`.
*
* @api private
*/

var CookieJar = exports = module.exports = function CookieJar() {
this.cookies = [];
};

/**
* Add the given `cookie` to the jar.
*
* @param {Cookie} cookie
* @api private
*/

CookieJar.prototype.add = function(cookie){
this.cookies = this.cookies.filter(function(c){
// Avoid duplication (same path, same name)
return !(c.name == cookie.name && c.path == cookie.path);
});
this.cookies.push(cookie);
};

/**
* Get cookies for the given `req`.
*
* @param {IncomingRequest} req
* @return {Array}
* @api private
*/

CookieJar.prototype.get = function(req){
var path = url.parse(req.url).pathname
, now = new Date
, specificity = {};
return this.cookies.filter(function(cookie){
if (0 == path.indexOf(cookie.path) && now < cookie.expires
&& cookie.path.length > (specificity[cookie.name] || 0))
return specificity[cookie.name] = cookie.path.length;
});
};

/**
* Return Cookie string for the given `req`.
*
* @param {IncomingRequest} req
* @return {String}
* @api private
*/

CookieJar.prototype.cookieString = function(req){
var cookies = this.get(req);
if (cookies.length) {
return cookies.map(function(cookie){
return cookie.name + '=' + cookie.value;
}).join('; ');
}
};

0 comments on commit 43cd887

Please sign in to comment.