From 4d40c327f0653e7ae0b7917821ae0183084f71ee Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 24 Oct 2015 19:47:46 -0500 Subject: [PATCH 1/4] Allow serialize function to accept objects of key-values --- README.md | 3 ++ index.js | 62 +++++++++++++++++++++++++-- test/serialize.js | 105 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 166 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9ef889e..15e9310 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 hdr = cookie.serialize({ foo: 'bar', cat: 'meow', dog: 'ruff' }) +// hdr = '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 89bd9cd..5650443 100644 --- a/index.js +++ b/index.js @@ -79,23 +79,79 @@ function parse(str, options) { return obj; } + + /** * 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 a cookie string suitable for http headers. An + * optional options object can be used to specify the encoding. If only + * one key-value pair is in the first parmater, then the options object can also + * specify cookie parameters. If more than one key-value pairs are in the + * first parameter, then the options object may only specify an encoding. + * + * 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' }) + * => "foo=bar; cat=meow" + * * @param {string} name * @param {string} val * @param {object} [options] * @return {string} * @public */ - 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 ''; + } else if(cookieNames.length > 1) { + // If there are more than one cookies to serialize, only allow + // an encoding option to be set + var opt = serializeOptions || {}; + serializeOptions = { + encode: opt.encode || encode + }; + } + + var serializedCookies = []; + cookieNames.forEach(function(cookieName) { + serializedCookies.push(serializeNameValue(cookieName, cookies[cookieName], serializeOptions)); + }); + + return serializedCookies.join('; '); + } else { + return serializeNameValue(name, val, options); + } +} + + +/** + * Serialize name value pair 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. + * + * serialize('foo', 'bar', { httpOnly: true }) + * => "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 6a04ff7..9e8893f 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.equal('foo=bar', cookie.serialize({ foo: 'bar' })); + assert.equal('foo=bar; cat=meow; dog=ruff', cookie.serialize({ foo: 'bar', cat: 'meow', dog: 'ruff' })); + assert.equal('foo=', cookie.serialize({ foo: '' })); + assert.equal('foo=; cat=meow', cookie.serialize({ foo: '', cat: 'meow' })); + assert.equal('', cookie.serialize({})); }); test('path', function() { @@ -18,6 +24,11 @@ test('path', function() { path: '/' })); + assert.equal('foo=bar; Path=/', cookie.serialize( + { foo: 'bar' }, + { path: '/' } + )); + assert.throws(cookie.serialize.bind(cookie, 'foo', 'bar', { path: '/\n' }), /option path is invalid/); @@ -31,6 +42,16 @@ test('secure', function() { assert.equal('foo=bar', cookie.serialize('foo', 'bar', { secure: false })); + + assert.equal('foo=bar; Secure', cookie.serialize( + { foo: 'bar' }, + { secure: true } + )); + + assert.equal('foo=bar', cookie.serialize( + { foo: 'bar' }, + { secure: false } + )); }); test('domain', function() { @@ -38,6 +59,11 @@ test('domain', function() { domain: 'example.com' })); + assert.equal('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/); @@ -47,6 +73,20 @@ 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.equal('foo=bar; HttpOnly', cookie.serialize( + { foo: 'bar' }, + { httpOnly: true } + )); + + assert.equal('foo=bar', cookie.serialize( + { foo: 'bar' }, + { httpOnly: false } + )); }); test('maxAge', function() { @@ -57,6 +97,16 @@ test('maxAge', function() { assert.equal('foo=bar; Max-Age=0', cookie.serialize('foo', 'bar', { maxAge: 0 })); + + assert.equal('foo=bar; Max-Age=1000', cookie.serialize( + { foo: 'bar' }, + { maxAge: 1000 } + )); + + assert.equal('foo=bar; Max-Age=0', cookie.serialize( + { foo: 'bar' }, + { maxAge: 0 } + )); }); test('firstPartyOnly', function() { @@ -67,10 +117,22 @@ test('firstPartyOnly', function() { assert.equal('foo=bar', cookie.serialize('foo', 'bar', { firstPartyOnly: false })); + + assert.equal('foo=bar; First-Party-Only', cookie.serialize( + {foo: 'bar'}, + {firstPartyOnly: true} + )); + + assert.equal('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() { @@ -80,6 +142,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' }))); + + assert.deepEqual({ cat: ' ";/' }, cookie.parse( + cookie.serialize({ cat: ' ";/' }))); +}); + +test('serialize->parse', function() { + + assert.equal('foo=bar; cat=meow; dog=ruff', cookie.serialize( + cookie.parse('foo=bar; cat=meow; dog=ruff'))); + + assert.equal('foo=bar', cookie.serialize( + cookie.parse('foo=bar'))); + + assert.equal('foo=; cat=meow', cookie.serialize( + cookie.parse('foo=; cat=meow'))); + }); test('unencoded', function() { @@ -90,4 +171,26 @@ 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; } + })); +}); + +test('too many options', function() { + + assert.equal('foo=bar; cat=meow; dog=ruff', cookie.serialize( + { foo: 'bar', cat: 'meow', dog: 'ruff' }, + { domain: 'example.com' })); + + assert.equal('cat=+ ; dog=, ', cookie.serialize( + { cat: '+ ', dog: ', ' }, + { domain: 'example.com', encode: function(value) { return value; } })); + +}); From 591675e6e3f51d55f611743495f3faadc2d086c2 Mon Sep 17 00:00:00 2001 From: Greg Date: Sat, 24 Oct 2015 23:10:14 -0500 Subject: [PATCH 2/4] Update tests to include exception testing --- test/serialize.js | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/test/serialize.js b/test/serialize.js index 9e8893f..e75e125 100644 --- a/test/serialize.js +++ b/test/serialize.js @@ -32,6 +32,11 @@ test('path', function() { 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() { @@ -67,6 +72,11 @@ test('domain', function() { 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() { @@ -119,8 +129,8 @@ test('firstPartyOnly', function() { })); assert.equal('foo=bar; First-Party-Only', cookie.serialize( - {foo: 'bar'}, - {firstPartyOnly: true} + { foo: 'bar' }, + { firstPartyOnly: true } )); assert.equal('foo=bar', cookie.serialize( @@ -131,8 +141,8 @@ test('firstPartyOnly', function() { 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: ', '})); + assert.deepEqual('cat=%2B%20', cookie.serialize({ cat: '+ ' })); + assert.deepEqual('cat=%2B%20; dog=%2C%20', cookie.serialize({ cat: '+ ', dog: ', ' })); }); test('parse->serialize', function() { @@ -181,9 +191,14 @@ test('unencoded', function() { { 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('too many options', function() { +test('many cookies many options', function() { assert.equal('foo=bar; cat=meow; dog=ruff', cookie.serialize( { foo: 'bar', cat: 'meow', dog: 'ruff' }, From 7df89fa36cd9a6c0de1bcfb3b8e5ff97cb6d606a Mon Sep 17 00:00:00 2001 From: Greg Date: Sun, 25 Oct 2015 13:35:06 -0500 Subject: [PATCH 3/4] Fix whitespace --- index.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 5650443..0701ff3 100644 --- a/index.js +++ b/index.js @@ -79,8 +79,6 @@ function parse(str, options) { return obj; } - - /** * Serialize data into a cookie header. * @@ -107,6 +105,7 @@ function parse(str, options) { * @return {string} * @public */ + function serialize(name, val, options) { if( typeof name === 'object') { var cookies = name; @@ -135,7 +134,6 @@ function serialize(name, val, options) { } } - /** * Serialize name value pair into a cookie header. * @@ -151,6 +149,7 @@ function serialize(name, val, options) { * @return {string} * @private */ + function serializeNameValue(name, val, options) { var opt = options || {}; var enc = opt.encode || encode; From 09ffac27a2cceedfcf95391cd521a4785bac5237 Mon Sep 17 00:00:00 2001 From: Greg Date: Mon, 26 Oct 2015 08:42:14 -0500 Subject: [PATCH 4/4] Update serialize to return array of cookie strings when given an object of key-values --- README.md | 4 ++-- index.js | 35 +++++++++++----------------- test/serialize.js | 59 +++++++++++++++++++++++++---------------------- 3 files changed, 46 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 15e9310..ada8e7d 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ var cookie = require('cookie'); var hdr = cookie.serialize('foo', 'bar'); // hdr = 'foo=bar'; -var hdr = cookie.serialize({ foo: 'bar', cat: 'meow', dog: 'ruff' }) -// hdr = 'foo=bar; cat=meow; dog=ruff' +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 0701ff3..c38d717 100644 --- a/index.js +++ b/index.js @@ -83,11 +83,9 @@ function parse(str, options) { * Serialize data into a cookie header. * * If the first parameter is an object, serialize the key-value pairs - * in the object into a cookie string suitable for http headers. An - * optional options object can be used to specify the encoding. If only - * one key-value pair is in the first parmater, then the options object can also - * specify cookie parameters. If more than one key-value pairs are in the - * first parameter, then the options object may only specify an encoding. + * 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 @@ -96,8 +94,8 @@ function parse(str, options) { * serialize('foo', 'bar', { httpOnly: true }) * => "foo=bar; httpOnly" * - * serialize({ foo: 'bar', cat: 'meow' }) - * => "foo=bar; cat=meow" + * serialize({ foo: 'bar', cat: 'meow' }, { httpOnly: true }) + * => ["foo=bar; httpOnly", "cat=meow; httpOnly"] * * @param {string} name * @param {string} val @@ -113,22 +111,15 @@ function serialize(name, val, options) { var cookieNames = Object.keys(cookies); if(0 === cookieNames.length) { - return ''; - } else if(cookieNames.length > 1) { - // If there are more than one cookies to serialize, only allow - // an encoding option to be set - var opt = serializeOptions || {}; - serializeOptions = { - encode: opt.encode || encode - }; + return undefined; + } else { + var serializedCookies = new Array(cookieNames.length); + for(var i=0; iserialize', function() { @@ -154,21 +154,21 @@ test('parse->serialize', function() { cookie.serialize('cat', ' ";/'))); assert.deepEqual({ cat: 'foo=123&name=baz five' }, cookie.parse( - cookie.serialize({ cat: 'foo=123&name=baz five' }))); + cookie.serialize({ cat: 'foo=123&name=baz five' })[0])); assert.deepEqual({ cat: ' ";/' }, cookie.parse( - cookie.serialize({ cat: ' ";/' }))); + cookie.serialize({ cat: ' ";/' })[0])); }); test('serialize->parse', function() { - assert.equal('foo=bar; cat=meow; dog=ruff', cookie.serialize( + assert.deepEqual(['foo=bar', 'cat=meow', 'dog=ruff'], cookie.serialize( cookie.parse('foo=bar; cat=meow; dog=ruff'))); - assert.equal('foo=bar', cookie.serialize( + assert.deepEqual(['foo=bar'], cookie.serialize( cookie.parse('foo=bar'))); - assert.equal('foo=; cat=meow', cookie.serialize( + assert.deepEqual(['foo=', 'cat=meow'], cookie.serialize( cookie.parse('foo=; cat=meow'))); }); @@ -182,12 +182,12 @@ test('unencoded', function() { encode: function(value) { return value; } }), /argument val is invalid/); - assert.deepEqual('cat=+ ', cookie.serialize( + assert.deepEqual(['cat=+ '], cookie.serialize( { cat: '+ ' }, { encode: function(value) { return value; } })); - assert.deepEqual('cat=+ ; dog=, ', cookie.serialize( + assert.deepEqual(['cat=+ ', 'dog=, '], cookie.serialize( { cat: '+ ', dog: ', ' }, { encode: function(value) { return value; } })); @@ -200,11 +200,14 @@ test('unencoded', function() { test('many cookies many options', function() { - assert.equal('foo=bar; cat=meow; dog=ruff', cookie.serialize( - { foo: 'bar', cat: 'meow', dog: 'ruff' }, - { domain: 'example.com' })); + 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.equal('cat=+ ; dog=, ', cookie.serialize( + assert.deepEqual(['cat=+ ; Domain=example.com', 'dog=, ; Domain=example.com'], cookie.serialize( { cat: '+ ', dog: ', ' }, { domain: 'example.com', encode: function(value) { return value; } }));