Skip to content

Commit

Permalink
fix: narrow the validation of cookies to match RFC6265 (#167)
Browse files Browse the repository at this point in the history
  • Loading branch information
bewinsnw authored Oct 1, 2024
1 parent 26031e3 commit e100428
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 9 deletions.
64 changes: 55 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,60 @@ exports.serialize = serialize;
var __toString = Object.prototype.toString

/**
* RegExp to match field-content in RFC 7230 sec 3.2
* RegExp to match cookie-name in RFC 6265 sec 4.1.1
* This refers out to the obsoleted definition of token in RFC 2616 sec 2.2
* which has been replaced by the token definition in RFC 7230 appendix B.
*
* field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ]
* field-vchar = VCHAR / obs-text
* obs-text = %x80-FF
* cookie-name = token
* token = 1*tchar
* tchar = "!" / "#" / "$" / "%" / "&" / "'" /
* "*" / "+" / "-" / "." / "^" / "_" /
* "`" / "|" / "~" / DIGIT / ALPHA
*/

var fieldContentRegExp = /^[\u0009\u0020-\u007e\u0080-\u00ff]+$/;
var cookieNameRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;

/**
* RegExp to match cookie-value in RFC 6265 sec 4.1.1
*
* cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
* cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
* ; US-ASCII characters excluding CTLs,
* ; whitespace DQUOTE, comma, semicolon,
* ; and backslash
*/

var cookieValueRegExp = /^("?)[\u0021\u0023-\u002B\u002D-\u003A\u003C-\u005B\u005D-\u007E]*\1$/;

/**
* RegExp to match domain-value in RFC 6265 sec 4.1.1
*
* domain-value = <subdomain>
* ; defined in [RFC1034], Section 3.5, as
* ; enhanced by [RFC1123], Section 2.1
* <subdomain> = <label> | <subdomain> "." <label>
* <label> = <let-dig> [ [ <ldh-str> ] <let-dig> ]
* Labels must be 63 characters or less.
* 'let-dig' not 'letter' in the first char, per RFC1123
* <ldh-str> = <let-dig-hyp> | <let-dig-hyp> <ldh-str>
* <let-dig-hyp> = <let-dig> | "-"
* <let-dig> = <letter> | <digit>
* <letter> = any one of the 52 alphabetic characters A through Z in
* upper case and a through z in lower case
* <digit> = any one of the ten digits 0 through 9
*/

var domainValueRegExp = /^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;

/**
* RegExp to match path-value in RFC 6265 sec 4.1.1
*
* path-value = <any CHAR except CTLs or ";">
* CHAR = %x01-7F
* ; defined in RFC 5234 appendix B.1
*/

var pathValueRegExp = /^[\u0020-\u003A\u003D-\u007E]*$/;

/**
* Parse a cookie header.
Expand Down Expand Up @@ -116,13 +162,13 @@ function serialize(name, val, options) {
throw new TypeError('option encode is invalid');
}

if (!fieldContentRegExp.test(name)) {
if (!cookieNameRegExp.test(name)) {
throw new TypeError('argument name is invalid');
}

var value = enc(val);

if (value && !fieldContentRegExp.test(value)) {
if (value && !cookieValueRegExp.test(value)) {
throw new TypeError('argument val is invalid');
}

Expand All @@ -139,15 +185,15 @@ function serialize(name, val, options) {
}

if (opt.domain) {
if (!fieldContentRegExp.test(opt.domain)) {
if (!domainValueRegExp.test(opt.domain)) {
throw new TypeError('option domain is invalid');
}

str += '; Domain=' + opt.domain;
}

if (opt.path) {
if (!fieldContentRegExp.test(opt.path)) {
if (!pathValueRegExp.test(opt.path)) {
throw new TypeError('option path is invalid');
}

Expand Down
4 changes: 4 additions & 0 deletions test/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ describe('cookie.serialize(name, value)', function () {
it('should throw for invalid name', function () {
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.throws(cookie.serialize.bind(cookie, 'foo bar', 'bar'), /argument name is invalid/)
})
})

Expand Down Expand Up @@ -52,6 +53,9 @@ describe('cookie.serialize(name, value, options)', function () {
assert.throws(cookie.serialize.bind(cookie, 'foo', '+ \n', {
encode: function (v) { return v }
}), /argument val is invalid/)
assert.throws(cookie.serialize.bind(cookie, 'foo', 'foo bar', {
encode: function (v) { return v }
}), /argument val is invalid/)
})
})

Expand Down

0 comments on commit e100428

Please sign in to comment.