diff --git a/README.md b/README.md index 9ef889e..ada8e7d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,9 @@ var cookie = require('cookie'); var hdr = cookie.serialize('foo', 'bar'); // hdr = 'foo=bar'; +var hdrs = cookie.serialize({ foo: 'bar', cat: 'meow', dog: 'ruff' }) +// hdrs = ['foo=bar', 'cat=meow', 'dog=ruff'] + var cookies = cookie.parse('foo=bar; cat=meow; dog=ruff'); // cookies = { foo: 'bar', cat: 'meow', dog: 'ruff' }; ``` diff --git a/index.js b/index.js index 88de23c..84a69b4 100644 --- a/index.js +++ b/index.js @@ -82,12 +82,21 @@ function parse(str, options) { /** * Serialize data into a cookie header. * - * Serialize the a name value pair into a cookie string suitable for - * http headers. An optional options object specified cookie parameters. + * If the first parameter is an object, serialize the key-value pairs + * in the object into an array of cookie strings suitable for http headers. + * An optional options object can be used to specify the encoding and cookie + * parameters. + * + * If the first parameter is a string, serialize the name value pair + * into a cookie string suitable for http headers. An optional options + * object specifies cookie parameters and encoding. * * serialize('foo', 'bar', { httpOnly: true }) * => "foo=bar; httpOnly" * + * serialize({ foo: 'bar', cat: 'meow' }, { httpOnly: true }) + * => ["foo=bar; httpOnly", "cat=meow; httpOnly"] + * * @param {string} name * @param {string} val * @param {object} [options] @@ -96,6 +105,43 @@ function parse(str, options) { */ function serialize(name, val, options) { + if( typeof name === 'object') { + var cookies = name; + var serializeOptions = val; + + var cookieNames = Object.keys(cookies); + if(0 === cookieNames.length) { + return undefined; + } else { + var serializedCookies = new Array(cookieNames.length); + for(var i=0; i "foo=bar; httpOnly" + * + * @param {string} name + * @param {string} val + * @param {object} [options] + * @return {string} + * @private + */ + +function serializeNameValue(name, val, options) { var opt = options || {}; var enc = opt.encode || encode; diff --git a/test/serialize.js b/test/serialize.js index fc7fdc4..05f187e 100644 --- a/test/serialize.js +++ b/test/serialize.js @@ -11,6 +11,12 @@ test('basic', function() { assert.equal('foo=', cookie.serialize('foo', '')); assert.throws(cookie.serialize.bind(cookie, 'foo\n', 'bar'), /argument name is invalid/); assert.throws(cookie.serialize.bind(cookie, 'foo\u280a', 'bar'), /argument name is invalid/); + + assert.deepEqual(['foo=bar'], cookie.serialize({ foo: 'bar' })); + assert.deepEqual(['foo=bar', 'cat=meow', 'dog=ruff'], cookie.serialize({ foo: 'bar', cat: 'meow', dog: 'ruff' })); + assert.deepEqual(['foo='], cookie.serialize({ foo: '' })); + assert.deepEqual(['foo=','cat=meow'], cookie.serialize({ foo: '', cat: 'meow' })); + assert.equal(undefined, cookie.serialize({})); }); test('path', function() { @@ -18,9 +24,19 @@ test('path', function() { path: '/' })); + assert.deepEqual(['foo=bar; Path=/'], cookie.serialize( + { foo: 'bar' }, + { path: '/' } + )); + assert.throws(cookie.serialize.bind(cookie, 'foo', 'bar', { path: '/\n' }), /option path is invalid/); + + assert.throws(cookie.serialize.bind(cookie, + { foo: 'bar' }, + { path: '/\n' }), + /option path is invalid/); }); test('secure', function() { @@ -31,6 +47,16 @@ test('secure', function() { assert.equal('foo=bar', cookie.serialize('foo', 'bar', { secure: false })); + + assert.deepEqual(['foo=bar; Secure'], cookie.serialize( + { foo: 'bar' }, + { secure: true } + )); + + assert.deepEqual(['foo=bar'], cookie.serialize( + { foo: 'bar' }, + { secure: false } + )); }); test('domain', function() { @@ -38,15 +64,39 @@ test('domain', function() { domain: 'example.com' })); + assert.deepEqual(['foo=bar; Domain=example.com'], cookie.serialize( + { foo: 'bar' }, + { domain: 'example.com' } + )); + assert.throws(cookie.serialize.bind(cookie, 'foo', 'bar', { domain: 'example.com\n' }), /option domain is invalid/); + + assert.throws(cookie.serialize.bind(cookie, + { foo: 'bar' }, + { domain: 'example.com\n' }), + /option domain is invalid/); }); test('httpOnly', function() { assert.equal('foo=bar; HttpOnly', cookie.serialize('foo', 'bar', { httpOnly: true })); + + assert.equal('foo=bar', cookie.serialize('foo', 'bar', { + httpOnly: false + })); + + assert.deepEqual(['foo=bar; HttpOnly'], cookie.serialize( + { foo: 'bar' }, + { httpOnly: true } + )); + + assert.deepEqual(['foo=bar'], cookie.serialize( + { foo: 'bar' }, + { httpOnly: false } + )); }); test('maxAge', function() { @@ -77,6 +127,16 @@ test('maxAge', function() { assert.equal('foo=bar; Max-Age=3', cookie.serialize('foo', 'bar', { maxAge: 3.14 })); + + assert.deepEqual(['foo=bar; Max-Age=1000'], cookie.serialize( + { foo: 'bar' }, + { maxAge: 1000 } + )); + + assert.deepEqual(['foo=bar; Max-Age=0'], cookie.serialize( + { foo: 'bar' }, + { maxAge: 0 } + )); }); test('firstPartyOnly', function() { @@ -87,10 +147,22 @@ test('firstPartyOnly', function() { assert.equal('foo=bar', cookie.serialize('foo', 'bar', { firstPartyOnly: false })); + + assert.deepEqual(['foo=bar; First-Party-Only'], cookie.serialize( + { foo: 'bar' }, + { firstPartyOnly: true } + )); + + assert.deepEqual(['foo=bar'], cookie.serialize( + { foo: 'bar' }, + { firstPartyOnly: false } + )); }); test('escaping', function() { assert.deepEqual('cat=%2B%20', cookie.serialize('cat', '+ ')); + assert.deepEqual(['cat=%2B%20'], cookie.serialize({ cat: '+ ' })); + assert.deepEqual(['cat=%2B%20', 'dog=%2C%20'], cookie.serialize({ cat: '+ ', dog: ', ' })); }); test('parse->serialize', function() { @@ -100,6 +172,25 @@ test('parse->serialize', function() { assert.deepEqual({ cat: ' ";/' }, cookie.parse( cookie.serialize('cat', ' ";/'))); + + assert.deepEqual({ cat: 'foo=123&name=baz five' }, cookie.parse( + cookie.serialize({ cat: 'foo=123&name=baz five' })[0])); + + assert.deepEqual({ cat: ' ";/' }, cookie.parse( + cookie.serialize({ cat: ' ";/' })[0])); +}); + +test('serialize->parse', function() { + + assert.deepEqual(['foo=bar', 'cat=meow', 'dog=ruff'], cookie.serialize( + cookie.parse('foo=bar; cat=meow; dog=ruff'))); + + assert.deepEqual(['foo=bar'], cookie.serialize( + cookie.parse('foo=bar'))); + + assert.deepEqual(['foo=', 'cat=meow'], cookie.serialize( + cookie.parse('foo=; cat=meow'))); + }); test('unencoded', function() { @@ -110,4 +201,34 @@ test('unencoded', function() { assert.throws(cookie.serialize.bind(cookie, 'cat', '+ \n', { encode: function(value) { return value; } }), /argument val is invalid/); -}) + + assert.deepEqual(['cat=+ '], cookie.serialize( + { cat: '+ ' }, + { encode: function(value) { return value; } + })); + + assert.deepEqual(['cat=+ ', 'dog=, '], cookie.serialize( + { cat: '+ ', dog: ', ' }, + { encode: function(value) { return value; } + })); + + assert.throws(cookie.serialize.bind(cookie, + { cat: '+ \n' }, + { encode: function(value) { return value; } }), + /argument val is invalid/); +}); + +test('many cookies many options', function() { + + assert.deepEqual( + ['foo=bar; Domain=example.com', 'cat=meow; Domain=example.com', 'dog=ruff; Domain=example.com'], + cookie.serialize( + { foo: 'bar', cat: 'meow', dog: 'ruff' }, + { domain: 'example.com' } + )); + + assert.deepEqual(['cat=+ ; Domain=example.com', 'dog=, ; Domain=example.com'], cookie.serialize( + { cat: '+ ', dog: ', ' }, + { domain: 'example.com', encode: function(value) { return value; } })); + +});