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: https://github.com/nodejs/node/issues/11256 PR-URL: https://github.com/nodejs/node/pull/11259 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Roman Reiss <me@silverwind.io> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
This commit is contained in:
parent
6b2cef65c9
commit
d3480776c7
@ -20,6 +20,27 @@ var RE_FIELDS = new RegExp('^(?:Connection|Transfer-Encoding|Content-Length|' +
|
|||||||
var RE_CONN_VALUES = /(?:^|\W)close|upgrade(?:$|\W)/ig;
|
var RE_CONN_VALUES = /(?:^|\W)close|upgrade(?:$|\W)/ig;
|
||||||
var RE_TE_CHUNKED = common.chunkExpression;
|
var RE_TE_CHUNKED = common.chunkExpression;
|
||||||
|
|
||||||
|
// isCookieField performs a case-insensitive comparison of a provided string
|
||||||
|
// against the word "cookie." This method (at least as of V8 5.4) is faster than
|
||||||
|
// the equivalent case-insensitive regexp, even if isCookieField does not get
|
||||||
|
// inlined.
|
||||||
|
function isCookieField(s) {
|
||||||
|
if (s.length !== 6) return false;
|
||||||
|
var ch = s.charCodeAt(0);
|
||||||
|
if (ch !== 99 && ch !== 67) return false;
|
||||||
|
ch = s.charCodeAt(1);
|
||||||
|
if (ch !== 111 && ch !== 79) return false;
|
||||||
|
ch = s.charCodeAt(2);
|
||||||
|
if (ch !== 111 && ch !== 79) return false;
|
||||||
|
ch = s.charCodeAt(3);
|
||||||
|
if (ch !== 107 && ch !== 75) return false;
|
||||||
|
ch = s.charCodeAt(4);
|
||||||
|
if (ch !== 105 && ch !== 73) return false;
|
||||||
|
ch = s.charCodeAt(5);
|
||||||
|
if (ch !== 101 && ch !== 69) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
var dateCache;
|
var dateCache;
|
||||||
function utcDate() {
|
function utcDate() {
|
||||||
if (!dateCache) {
|
if (!dateCache) {
|
||||||
@ -275,13 +296,15 @@ function _storeHeader(firstLine, headers) {
|
|||||||
value = entry[1];
|
value = entry[1];
|
||||||
|
|
||||||
if (value instanceof Array) {
|
if (value instanceof Array) {
|
||||||
for (j = 0; j < value.length; j++) {
|
if (value.length < 2 || !isCookieField(field)) {
|
||||||
|
for (j = 0; j < value.length; j++)
|
||||||
storeHeader(this, state, field, value[j], false);
|
storeHeader(this, state, field, value[j], false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
value = value.join('; ');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
storeHeader(this, state, field, value, false);
|
storeHeader(this, state, field, value, false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (headers instanceof Array) {
|
} else if (headers instanceof Array) {
|
||||||
for (i = 0; i < headers.length; i++) {
|
for (i = 0; i < headers.length; i++) {
|
||||||
field = headers[i][0];
|
field = headers[i][0];
|
||||||
@ -302,14 +325,16 @@ function _storeHeader(firstLine, headers) {
|
|||||||
value = headers[field];
|
value = headers[field];
|
||||||
|
|
||||||
if (value instanceof Array) {
|
if (value instanceof Array) {
|
||||||
for (j = 0; j < value.length; j++) {
|
if (value.length < 2 || !isCookieField(field)) {
|
||||||
|
for (j = 0; j < value.length; j++)
|
||||||
storeHeader(this, state, field, value[j], true);
|
storeHeader(this, state, field, value[j], true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
value = value.join('; ');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
storeHeader(this, state, field, value, true);
|
storeHeader(this, state, field, value, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Are we upgrading the connection?
|
// Are we upgrading the connection?
|
||||||
if (state.connUpgrade && state.upgrade)
|
if (state.connUpgrade && state.upgrade)
|
||||||
|
@ -1,84 +1,113 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
require('../common');
|
const common = require('../common');
|
||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
|
||||||
let responses_sent = 0;
|
const expectedRequests = ['/hello', '/there', '/world'];
|
||||||
let responses_recvd = 0;
|
|
||||||
let body0 = '';
|
|
||||||
let body1 = '';
|
|
||||||
|
|
||||||
const server = http.Server(function(req, res) {
|
const server = http.Server(common.mustCall(function(req, res) {
|
||||||
if (responses_sent === 0) {
|
assert.strictEqual(expectedRequests.shift(), req.url);
|
||||||
assert.strictEqual('GET', req.method);
|
|
||||||
assert.strictEqual('/hello', url.parse(req.url).pathname);
|
|
||||||
|
|
||||||
console.dir(req.headers);
|
switch (req.url) {
|
||||||
assert.strictEqual(true, 'accept' in req.headers);
|
case '/hello':
|
||||||
assert.strictEqual('*/*', req.headers['accept']);
|
assert.strictEqual(req.method, 'GET');
|
||||||
|
assert.strictEqual(req.headers['accept'], '*/*');
|
||||||
assert.strictEqual(true, 'foo' in req.headers);
|
assert.strictEqual(req.headers['foo'], 'bar');
|
||||||
assert.strictEqual('bar', req.headers['foo']);
|
assert.strictEqual(req.headers.cookie, 'foo=bar; bar=baz; baz=quux');
|
||||||
|
break;
|
||||||
|
case '/there':
|
||||||
|
assert.strictEqual(req.method, 'PUT');
|
||||||
|
assert.strictEqual(req.headers.cookie, 'node=awesome; ta=da');
|
||||||
|
break;
|
||||||
|
case '/world':
|
||||||
|
assert.strictEqual(req.method, 'POST');
|
||||||
|
assert.deepStrictEqual(req.headers.cookie, 'abc=123; def=456; ghi=789');
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
assert(false, `Unexpected request for ${req.url}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (responses_sent === 1) {
|
if (expectedRequests.length === 0)
|
||||||
assert.strictEqual('POST', req.method);
|
|
||||||
assert.strictEqual('/world', url.parse(req.url).pathname);
|
|
||||||
this.close();
|
this.close();
|
||||||
}
|
|
||||||
|
|
||||||
req.on('end', function() {
|
req.on('end', function() {
|
||||||
res.writeHead(200, {'Content-Type': 'text/plain'});
|
res.writeHead(200, {'Content-Type': 'text/plain'});
|
||||||
res.write('The path was ' + url.parse(req.url).pathname);
|
res.write('The path was ' + url.parse(req.url).pathname);
|
||||||
res.end();
|
res.end();
|
||||||
responses_sent += 1;
|
|
||||||
});
|
});
|
||||||
req.resume();
|
req.resume();
|
||||||
|
}, 3));
|
||||||
//assert.strictEqual('127.0.0.1', res.connection.remoteAddress);
|
|
||||||
});
|
|
||||||
server.listen(0);
|
server.listen(0);
|
||||||
|
|
||||||
server.on('listening', function() {
|
server.on('listening', function() {
|
||||||
const agent = new http.Agent({ port: this.address().port, maxSockets: 1 });
|
const agent = new http.Agent({ port: this.address().port, maxSockets: 1 });
|
||||||
http.get({
|
const req = http.get({
|
||||||
port: this.address().port,
|
port: this.address().port,
|
||||||
path: '/hello',
|
path: '/hello',
|
||||||
headers: {'Accept': '*/*', 'Foo': 'bar'},
|
headers: {
|
||||||
|
Accept: '*/*',
|
||||||
|
Foo: 'bar',
|
||||||
|
Cookie: [ 'foo=bar', 'bar=baz', 'baz=quux' ]
|
||||||
|
},
|
||||||
agent: agent
|
agent: agent
|
||||||
}, function(res) {
|
}, common.mustCall(function(res) {
|
||||||
assert.strictEqual(200, res.statusCode);
|
const cookieHeaders = req._header.match(/^Cookie: .+$/img);
|
||||||
responses_recvd += 1;
|
assert.deepStrictEqual(cookieHeaders,
|
||||||
|
['Cookie: foo=bar; bar=baz; baz=quux']);
|
||||||
|
assert.strictEqual(res.statusCode, 200);
|
||||||
|
let body = '';
|
||||||
res.setEncoding('utf8');
|
res.setEncoding('utf8');
|
||||||
res.on('data', function(chunk) { body0 += chunk; });
|
res.on('data', function(chunk) { body += chunk; });
|
||||||
console.error('Got /hello response');
|
res.on('end', common.mustCall(function() {
|
||||||
});
|
assert.strictEqual(body, 'The path was /hello');
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
|
||||||
setTimeout(function() {
|
setTimeout(common.mustCall(function() {
|
||||||
|
const req = http.request({
|
||||||
|
port: server.address().port,
|
||||||
|
method: 'PUT',
|
||||||
|
path: '/there',
|
||||||
|
agent: agent
|
||||||
|
}, common.mustCall(function(res) {
|
||||||
|
const cookieHeaders = req._header.match(/^Cookie: .+$/img);
|
||||||
|
assert.deepStrictEqual(cookieHeaders, ['Cookie: node=awesome; ta=da']);
|
||||||
|
assert.strictEqual(res.statusCode, 200);
|
||||||
|
let body = '';
|
||||||
|
res.setEncoding('utf8');
|
||||||
|
res.on('data', function(chunk) { body += chunk; });
|
||||||
|
res.on('end', common.mustCall(function() {
|
||||||
|
assert.strictEqual(body, 'The path was /there');
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
req.setHeader('Cookie', ['node=awesome', 'ta=da']);
|
||||||
|
req.end();
|
||||||
|
}), 1);
|
||||||
|
|
||||||
|
setTimeout(common.mustCall(function() {
|
||||||
const req = http.request({
|
const req = http.request({
|
||||||
port: server.address().port,
|
port: server.address().port,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/world',
|
path: '/world',
|
||||||
|
headers: [ ['Cookie', 'abc=123'],
|
||||||
|
['Cookie', 'def=456'],
|
||||||
|
['Cookie', 'ghi=789'] ],
|
||||||
agent: agent
|
agent: agent
|
||||||
}, function(res) {
|
}, common.mustCall(function(res) {
|
||||||
assert.strictEqual(200, res.statusCode);
|
const cookieHeaders = req._header.match(/^Cookie: .+$/img);
|
||||||
responses_recvd += 1;
|
assert.deepStrictEqual(cookieHeaders,
|
||||||
|
['Cookie: abc=123',
|
||||||
|
'Cookie: def=456',
|
||||||
|
'Cookie: ghi=789']);
|
||||||
|
assert.strictEqual(res.statusCode, 200);
|
||||||
|
let body = '';
|
||||||
res.setEncoding('utf8');
|
res.setEncoding('utf8');
|
||||||
res.on('data', function(chunk) { body1 += chunk; });
|
res.on('data', function(chunk) { body += chunk; });
|
||||||
console.error('Got /world response');
|
res.on('end', common.mustCall(function() {
|
||||||
});
|
assert.strictEqual(body, 'The path was /world');
|
||||||
|
}));
|
||||||
|
}));
|
||||||
req.end();
|
req.end();
|
||||||
}, 1);
|
}), 2);
|
||||||
});
|
|
||||||
|
|
||||||
process.on('exit', function() {
|
|
||||||
console.error('responses_recvd: ' + responses_recvd);
|
|
||||||
assert.strictEqual(2, responses_recvd);
|
|
||||||
|
|
||||||
console.error('responses_sent: ' + responses_sent);
|
|
||||||
assert.strictEqual(2, responses_sent);
|
|
||||||
|
|
||||||
assert.strictEqual('The path was /hello', body0);
|
|
||||||
assert.strictEqual('The path was /world', body1);
|
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user