Skip to content

Commit d348077

Browse files
committed
http: concatenate outgoing Cookie headers
This commit enables automatic concatenation of multiple Cookie header values with a semicolon, except when 2D header arrays are used. Fixes: #11256 PR-URL: #11259 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Roman Reiss <[email protected]> Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]>
1 parent 6b2cef6 commit d348077

File tree

2 files changed

+112
-58
lines changed

2 files changed

+112
-58
lines changed

lib/_http_outgoing.js

+33-8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,27 @@ var RE_FIELDS = new RegExp('^(?:Connection|Transfer-Encoding|Content-Length|' +
2020
var RE_CONN_VALUES = /(?:^|\W)close|upgrade(?:$|\W)/ig;
2121
var RE_TE_CHUNKED = common.chunkExpression;
2222

23+
// isCookieField performs a case-insensitive comparison of a provided string
24+
// against the word "cookie." This method (at least as of V8 5.4) is faster than
25+
// the equivalent case-insensitive regexp, even if isCookieField does not get
26+
// inlined.
27+
function isCookieField(s) {
28+
if (s.length !== 6) return false;
29+
var ch = s.charCodeAt(0);
30+
if (ch !== 99 && ch !== 67) return false;
31+
ch = s.charCodeAt(1);
32+
if (ch !== 111 && ch !== 79) return false;
33+
ch = s.charCodeAt(2);
34+
if (ch !== 111 && ch !== 79) return false;
35+
ch = s.charCodeAt(3);
36+
if (ch !== 107 && ch !== 75) return false;
37+
ch = s.charCodeAt(4);
38+
if (ch !== 105 && ch !== 73) return false;
39+
ch = s.charCodeAt(5);
40+
if (ch !== 101 && ch !== 69) return false;
41+
return true;
42+
}
43+
2344
var dateCache;
2445
function utcDate() {
2546
if (!dateCache) {
@@ -275,12 +296,14 @@ function _storeHeader(firstLine, headers) {
275296
value = entry[1];
276297

277298
if (value instanceof Array) {
278-
for (j = 0; j < value.length; j++) {
279-
storeHeader(this, state, field, value[j], false);
299+
if (value.length < 2 || !isCookieField(field)) {
300+
for (j = 0; j < value.length; j++)
301+
storeHeader(this, state, field, value[j], false);
302+
continue;
280303
}
281-
} else {
282-
storeHeader(this, state, field, value, false);
304+
value = value.join('; ');
283305
}
306+
storeHeader(this, state, field, value, false);
284307
}
285308
} else if (headers instanceof Array) {
286309
for (i = 0; i < headers.length; i++) {
@@ -302,12 +325,14 @@ function _storeHeader(firstLine, headers) {
302325
value = headers[field];
303326

304327
if (value instanceof Array) {
305-
for (j = 0; j < value.length; j++) {
306-
storeHeader(this, state, field, value[j], true);
328+
if (value.length < 2 || !isCookieField(field)) {
329+
for (j = 0; j < value.length; j++)
330+
storeHeader(this, state, field, value[j], true);
331+
continue;
307332
}
308-
} else {
309-
storeHeader(this, state, field, value, true);
333+
value = value.join('; ');
310334
}
335+
storeHeader(this, state, field, value, true);
311336
}
312337
}
313338

test/parallel/test-http.js

+79-50
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,113 @@
11
'use strict';
2-
require('../common');
2+
const common = require('../common');
33
const assert = require('assert');
44
const http = require('http');
55
const url = require('url');
66

7-
let responses_sent = 0;
8-
let responses_recvd = 0;
9-
let body0 = '';
10-
let body1 = '';
7+
const expectedRequests = ['/hello', '/there', '/world'];
118

12-
const server = http.Server(function(req, res) {
13-
if (responses_sent === 0) {
14-
assert.strictEqual('GET', req.method);
15-
assert.strictEqual('/hello', url.parse(req.url).pathname);
9+
const server = http.Server(common.mustCall(function(req, res) {
10+
assert.strictEqual(expectedRequests.shift(), req.url);
1611

17-
console.dir(req.headers);
18-
assert.strictEqual(true, 'accept' in req.headers);
19-
assert.strictEqual('*/*', req.headers['accept']);
20-
21-
assert.strictEqual(true, 'foo' in req.headers);
22-
assert.strictEqual('bar', req.headers['foo']);
12+
switch (req.url) {
13+
case '/hello':
14+
assert.strictEqual(req.method, 'GET');
15+
assert.strictEqual(req.headers['accept'], '*/*');
16+
assert.strictEqual(req.headers['foo'], 'bar');
17+
assert.strictEqual(req.headers.cookie, 'foo=bar; bar=baz; baz=quux');
18+
break;
19+
case '/there':
20+
assert.strictEqual(req.method, 'PUT');
21+
assert.strictEqual(req.headers.cookie, 'node=awesome; ta=da');
22+
break;
23+
case '/world':
24+
assert.strictEqual(req.method, 'POST');
25+
assert.deepStrictEqual(req.headers.cookie, 'abc=123; def=456; ghi=789');
26+
break;
27+
default:
28+
assert(false, `Unexpected request for ${req.url}`);
2329
}
2430

25-
if (responses_sent === 1) {
26-
assert.strictEqual('POST', req.method);
27-
assert.strictEqual('/world', url.parse(req.url).pathname);
31+
if (expectedRequests.length === 0)
2832
this.close();
29-
}
3033

3134
req.on('end', function() {
3235
res.writeHead(200, {'Content-Type': 'text/plain'});
3336
res.write('The path was ' + url.parse(req.url).pathname);
3437
res.end();
35-
responses_sent += 1;
3638
});
3739
req.resume();
38-
39-
//assert.strictEqual('127.0.0.1', res.connection.remoteAddress);
40-
});
40+
}, 3));
4141
server.listen(0);
4242

4343
server.on('listening', function() {
4444
const agent = new http.Agent({ port: this.address().port, maxSockets: 1 });
45-
http.get({
45+
const req = http.get({
4646
port: this.address().port,
4747
path: '/hello',
48-
headers: {'Accept': '*/*', 'Foo': 'bar'},
48+
headers: {
49+
Accept: '*/*',
50+
Foo: 'bar',
51+
Cookie: [ 'foo=bar', 'bar=baz', 'baz=quux' ]
52+
},
4953
agent: agent
50-
}, function(res) {
51-
assert.strictEqual(200, res.statusCode);
52-
responses_recvd += 1;
54+
}, common.mustCall(function(res) {
55+
const cookieHeaders = req._header.match(/^Cookie: .+$/img);
56+
assert.deepStrictEqual(cookieHeaders,
57+
['Cookie: foo=bar; bar=baz; baz=quux']);
58+
assert.strictEqual(res.statusCode, 200);
59+
let body = '';
5360
res.setEncoding('utf8');
54-
res.on('data', function(chunk) { body0 += chunk; });
55-
console.error('Got /hello response');
56-
});
61+
res.on('data', function(chunk) { body += chunk; });
62+
res.on('end', common.mustCall(function() {
63+
assert.strictEqual(body, 'The path was /hello');
64+
}));
65+
}));
5766

58-
setTimeout(function() {
67+
setTimeout(common.mustCall(function() {
68+
const req = http.request({
69+
port: server.address().port,
70+
method: 'PUT',
71+
path: '/there',
72+
agent: agent
73+
}, common.mustCall(function(res) {
74+
const cookieHeaders = req._header.match(/^Cookie: .+$/img);
75+
assert.deepStrictEqual(cookieHeaders, ['Cookie: node=awesome; ta=da']);
76+
assert.strictEqual(res.statusCode, 200);
77+
let body = '';
78+
res.setEncoding('utf8');
79+
res.on('data', function(chunk) { body += chunk; });
80+
res.on('end', common.mustCall(function() {
81+
assert.strictEqual(body, 'The path was /there');
82+
}));
83+
}));
84+
req.setHeader('Cookie', ['node=awesome', 'ta=da']);
85+
req.end();
86+
}), 1);
87+
88+
setTimeout(common.mustCall(function() {
5989
const req = http.request({
6090
port: server.address().port,
6191
method: 'POST',
6292
path: '/world',
93+
headers: [ ['Cookie', 'abc=123'],
94+
['Cookie', 'def=456'],
95+
['Cookie', 'ghi=789'] ],
6396
agent: agent
64-
}, function(res) {
65-
assert.strictEqual(200, res.statusCode);
66-
responses_recvd += 1;
97+
}, common.mustCall(function(res) {
98+
const cookieHeaders = req._header.match(/^Cookie: .+$/img);
99+
assert.deepStrictEqual(cookieHeaders,
100+
['Cookie: abc=123',
101+
'Cookie: def=456',
102+
'Cookie: ghi=789']);
103+
assert.strictEqual(res.statusCode, 200);
104+
let body = '';
67105
res.setEncoding('utf8');
68-
res.on('data', function(chunk) { body1 += chunk; });
69-
console.error('Got /world response');
70-
});
106+
res.on('data', function(chunk) { body += chunk; });
107+
res.on('end', common.mustCall(function() {
108+
assert.strictEqual(body, 'The path was /world');
109+
}));
110+
}));
71111
req.end();
72-
}, 1);
73-
});
74-
75-
process.on('exit', function() {
76-
console.error('responses_recvd: ' + responses_recvd);
77-
assert.strictEqual(2, responses_recvd);
78-
79-
console.error('responses_sent: ' + responses_sent);
80-
assert.strictEqual(2, responses_sent);
81-
82-
assert.strictEqual('The path was /hello', body0);
83-
assert.strictEqual('The path was /world', body1);
112+
}), 2);
84113
});

0 commit comments

Comments
 (0)