Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serialize objects of key/value pairs #47

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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' };
```
Expand Down
50 changes: 48 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -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<cookieNames.length; i++) {
serializedCookies[i] = serializeNameValue(cookieNames[i], cookies[cookieNames[i]], serializeOptions);
}

return serializedCookies;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if given a single key-value pair in the object, this returns an array of serialized cookie strings. I thought this might make the function more consistent in that if you pass an object, you always get back an array of strings rather than returning just a single string if you give only a single key-value pair in the object and an array if you give multiple key-value pairs in the object.

I still left the behavior that when given a name string and a value string, the function returns a single serialized cookie string so this does not break anyone's code that uses the old version of this function.

}
} 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;

Expand Down
123 changes: 122 additions & 1 deletion test/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,32 @@ 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() {
assert.equal('foo=bar; Path=/', cookie.serialize('foo', 'bar', {
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() {
Expand All @@ -31,22 +47,56 @@ 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() {
assert.equal('foo=bar; Domain=example.com', cookie.serialize('foo', 'bar', {
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() {
Expand Down Expand Up @@ -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() {
Expand All @@ -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() {
Expand All @@ -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() {
Expand All @@ -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; } }));

});