Skip to content

Commit

Permalink
Merge pull request #49 from Sebmaster/fix/empty-keys
Browse files Browse the repository at this point in the history
Enable parsing cookies with an empty or no key
  • Loading branch information
stash-sfdc committed Oct 2, 2015
2 parents a3af610 + aba0def commit a3ef7fc
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 7 deletions.
28 changes: 21 additions & 7 deletions lib/cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,18 @@ var DATE_DELIM = /[\x09\x20-\x2F\x3B-\x40\x5B-\x60\x7B-\x7E]/;
// From RFC6265 S4.1.1
// note that it excludes \x3B ";"
var COOKIE_OCTET = /[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]/;
var COOKIE_OCTETS = new RegExp('^'+COOKIE_OCTET.source+'$');
var COOKIE_OCTETS = new RegExp('^'+COOKIE_OCTET.source+'+$');

var CONTROL_CHARS = /[\x00-\x1F]/;

// Double quotes are part of the value (see: S4.1.1).
// '\r', '\n' and '\0' should be treated as a terminator in the "relaxed" mode
// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L60)
// '=' and ';' are attribute/values separators
// (see: https://github.com/ChromiumWebApps/chromium/blob/b3d3b4da8bb94c1b2e061600df106d590fda3620/net/cookies/parsed_cookie.cc#L64)
var COOKIE_PAIR = /^([^=;]+)\s*=\s*(("?)[^\n\r\0]*\3)/;
var COOKIE_PAIR = /^(([^=;]+))\s*=\s*(("?)[^\n\r\0]*\3)/

var LOOSE_COOKIE_PAIR = /^((?:=)?([^=;]*)\s*=\s*)?(("?)[^\n\r\0]*\3)/;

// RFC6265 S4.1.1 defines path value as 'any CHAR except CTLs or ";"'
// Note ';' is \x3B
Expand Down Expand Up @@ -315,7 +319,7 @@ function defaultPath(path) {
}


function parse(str) {
function parse(str, opts) {
str = str.trim();

// S4.1.1 Trailing semi-colons are not part of the specification.
Expand All @@ -326,7 +330,7 @@ function parse(str) {

// We use a regex to parse the "name-value-pair" part of S5.2
var firstSemi = str.indexOf(';'); // S5.2 step 1
var result = COOKIE_PAIR.exec(firstSemi === -1 ? str : str.substr(0,firstSemi));
var result = (opts && opts.loose ? LOOSE_COOKIE_PAIR : COOKIE_PAIR).exec(firstSemi === -1 ? str : str.substr(0,firstSemi));

// Rx satisfies the "the name string is empty" and "lacks a %x3D ("=")"
// constraints as well as trimming any whitespace.
Expand All @@ -335,8 +339,15 @@ function parse(str) {
}

var c = new Cookie();
c.key = result[1].trim();
c.value = result[2].trim();
if (result[1]) {
c.key = result[2].trim();
} else {
c.key = '';
}
c.value = result[3].trim();
if (CONTROL_CHARS.test(c.key) || CONTROL_CHARS.test(c.value)) {
return;
}

if (firstSemi === -1) {
return c;
Expand Down Expand Up @@ -745,6 +756,9 @@ Cookie.prototype.cookieString = function cookieString() {
if (val == null) {
val = '';
}
if (this.key === '') {
return val;
}
return this.key+'='+val;
};

Expand Down Expand Up @@ -885,7 +899,7 @@ CookieJar.prototype.setCookie = function(cookie, url, options, cb) {

// S5.3 step 1
if (!(cookie instanceof Cookie)) {
cookie = Cookie.parse(cookie);
cookie = Cookie.parse(cookie, { loose: options.loose });
}
if (!cookie) {
err = new Error("Cookie failed to parse");
Expand Down
36 changes: 36 additions & 0 deletions test/api_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,4 +369,40 @@ vows
}
}
})
.addBatch({
"loose option": {
"cookie jar with loose": {
topic: function () {
var jar = new CookieJar();
try {
var url = 'http://example.com/index.html';
jar.setCookieSync("=b", url, { loose: true });
return jar.getCookieStringSync(url);
} catch (e) {
return e;
}
},
"fails": function (err, val) {
assert.equal(err, null);
assert.equal(val, 'b');
}
},
"cookie jar without loose": {
topic: function () {
var jar = new CookieJar();
try {
var url = 'http://example.com/index.html';
jar.setCookieSync("=b", url);
return jar.getCookieStringSync(url);
} catch (e) {
return e;
}
},
"fails": function (err) {
assert.instanceOf(err, Error);
assert.equal(err.message, 'Cookie failed to parse');
}
}
}
})
.export(module);
33 changes: 33 additions & 0 deletions test/parsing_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,39 @@ vows
"no path": function(c) { assert.equal(c.path, null) },
"no domain": function(c) { assert.equal(c.domain, null) },
"no extensions": function(c) { assert.ok(!c.extensions) }
},
"empty key": {
topic: function() {
return Cookie.parse('=abc', { loose: true }) || null;
},
"parsed": function(c) { assert.ok(c) },
"key": function(c) { assert.equal(c.key, '') },
"value": function(c) { assert.equal(c.value, 'abc') },
"no path": function(c) { assert.equal(c.path, null) },
"no domain": function(c) { assert.equal(c.domain, null) },
"no extensions": function(c) { assert.ok(!c.extensions) }
},
"non-existent key": {
topic: function() {
return Cookie.parse('abc', { loose: true }) || null;
},
"parsed": function(c) { assert.ok(c) },
"key": function(c) { assert.equal(c.key, '') },
"value": function(c) { assert.equal(c.value, 'abc') },
"no path": function(c) { assert.equal(c.path, null) },
"no domain": function(c) { assert.equal(c.domain, null) },
"no extensions": function(c) { assert.ok(!c.extensions) }
},
"weird format": {
topic: function() {
return Cookie.parse('=foo=bar', { loose: true }) || null;
},
"parsed": function(c) { assert.ok(c) },
"key": function(c) { assert.equal(c.key, 'foo') },
"value": function(c) { assert.equal(c.value, 'bar') },
"no path": function(c) { assert.equal(c.path, null) },
"no domain": function(c) { assert.equal(c.domain, null) },
"no extensions": function(c) { assert.ok(!c.extensions) }
}
})
.export(module);

0 comments on commit a3ef7fc

Please sign in to comment.