Browse Source

http: fix connection upgrade checks

This commit fixes connection upgrade checks, specifically when headers
are passed as an array instead of a plain object to http.request()

Fixes: https://github.com/nodejs/node/issues/8235
PR-URL: https://github.com/nodejs/node/pull/8238
Reviewed-By: James M Snell <jasnell@gmail.com>
v6.x
Brian White 9 years ago
committed by Jeremiah Senkpiel
parent
commit
fa4c4d655a
  1. 8
      lib/_http_common.js
  2. 24
      lib/_http_outgoing.js
  3. 36
      test/parallel/test-http-upgrade-client.js

8
lib/_http_common.js

@ -80,17 +80,11 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
parser.incoming.statusMessage = statusMessage; parser.incoming.statusMessage = statusMessage;
} }
if (upgrade && parser.outgoing !== null && !parser.outgoing.upgrading) {
// The client made non-upgrade request, and server is just advertising // The client made non-upgrade request, and server is just advertising
// supported protocols. // supported protocols.
// //
// See RFC7230 Section 6.7 // See RFC7230 Section 6.7
//
// NOTE: RegExp below matches `upgrade` in `Connection: abc, upgrade, def`
// header.
if (upgrade &&
parser.outgoing !== null &&
(parser.outgoing._headers.upgrade === undefined ||
!/(^|\W)upgrade(\W|$)/i.test(parser.outgoing._headers.connection))) {
upgrade = false; upgrade = false;
} }

24
lib/_http_outgoing.js

@ -9,16 +9,18 @@ const Buffer = require('buffer').Buffer;
const common = require('_http_common'); const common = require('_http_common');
const CRLF = common.CRLF; const CRLF = common.CRLF;
const chunkExpression = common.chunkExpression; const trfrEncChunkExpression = common.chunkExpression;
const debug = common.debug; const debug = common.debug;
const connectionExpression = /^Connection$/i; const upgradeExpression = /^Upgrade$/i;
const transferEncodingExpression = /^Transfer-Encoding$/i; const transferEncodingExpression = /^Transfer-Encoding$/i;
const closeExpression = /close/i;
const contentLengthExpression = /^Content-Length$/i; const contentLengthExpression = /^Content-Length$/i;
const dateExpression = /^Date$/i; const dateExpression = /^Date$/i;
const expectExpression = /^Expect$/i; const expectExpression = /^Expect$/i;
const trailerExpression = /^Trailer$/i; const trailerExpression = /^Trailer$/i;
const connectionExpression = /^Connection$/i;
const connCloseExpression = /(^|\W)close(\W|$)/i;
const connUpgradeExpression = /(^|\W)upgrade(\W|$)/i;
const automaticHeaders = { const automaticHeaders = {
connection: true, connection: true,
@ -61,6 +63,7 @@ function OutgoingMessage() {
this.writable = true; this.writable = true;
this._last = false; this._last = false;
this.upgrading = false;
this.chunkedEncoding = false; this.chunkedEncoding = false;
this.shouldKeepAlive = true; this.shouldKeepAlive = true;
this.useChunkedEncodingByDefault = true; this.useChunkedEncodingByDefault = true;
@ -192,11 +195,13 @@ OutgoingMessage.prototype._storeHeader = function(firstLine, headers) {
// in the case of response it is: 'HTTP/1.1 200 OK\r\n' // in the case of response it is: 'HTTP/1.1 200 OK\r\n'
var state = { var state = {
sentConnectionHeader: false, sentConnectionHeader: false,
sentConnectionUpgrade: false,
sentContentLengthHeader: false, sentContentLengthHeader: false,
sentTransferEncodingHeader: false, sentTransferEncodingHeader: false,
sentDateHeader: false, sentDateHeader: false,
sentExpect: false, sentExpect: false,
sentTrailer: false, sentTrailer: false,
sentUpgrade: false,
messageHeader: firstLine messageHeader: firstLine
}; };
@ -225,6 +230,10 @@ OutgoingMessage.prototype._storeHeader = function(firstLine, headers) {
} }
} }
// Are we upgrading the connection?
if (state.sentConnectionUpgrade && state.sentUpgrade)
this.upgrading = true;
// Date header // Date header
if (this.sendDate === true && state.sentDateHeader === false) { if (this.sendDate === true && state.sentDateHeader === false) {
state.messageHeader += 'Date: ' + utcDate() + CRLF; state.messageHeader += 'Date: ' + utcDate() + CRLF;
@ -312,15 +321,16 @@ function storeHeader(self, state, field, value) {
if (connectionExpression.test(field)) { if (connectionExpression.test(field)) {
state.sentConnectionHeader = true; state.sentConnectionHeader = true;
if (closeExpression.test(value)) { if (connCloseExpression.test(value)) {
self._last = true; self._last = true;
} else { } else {
self.shouldKeepAlive = true; self.shouldKeepAlive = true;
} }
if (connUpgradeExpression.test(value))
state.sentConnectionUpgrade = true;
} else if (transferEncodingExpression.test(field)) { } else if (transferEncodingExpression.test(field)) {
state.sentTransferEncodingHeader = true; state.sentTransferEncodingHeader = true;
if (chunkExpression.test(value)) self.chunkedEncoding = true; if (trfrEncChunkExpression.test(value)) self.chunkedEncoding = true;
} else if (contentLengthExpression.test(field)) { } else if (contentLengthExpression.test(field)) {
state.sentContentLengthHeader = true; state.sentContentLengthHeader = true;
@ -330,6 +340,8 @@ function storeHeader(self, state, field, value) {
state.sentExpect = true; state.sentExpect = true;
} else if (trailerExpression.test(field)) { } else if (trailerExpression.test(field)) {
state.sentTrailer = true; state.sentTrailer = true;
} else if (upgradeExpression.test(field)) {
state.sentUpgrade = true;
} }
} }

36
test/parallel/test-http-upgrade-client.js

@ -26,15 +26,28 @@ var srv = net.createServer(function(c) {
}); });
srv.listen(0, '127.0.0.1', common.mustCall(function() { srv.listen(0, '127.0.0.1', common.mustCall(function() {
var port = this.address().port;
var req = http.get({ var headers = [
port: this.address().port, {
headers: {
connection: 'upgrade', connection: 'upgrade',
upgrade: 'websocket' upgrade: 'websocket'
} },
[
['Host', 'echo.websocket.org'],
['Connection', 'Upgrade'],
['Upgrade', 'websocket'],
['Origin', 'http://www.websocket.org']
]
];
var left = headers.length;
headers.forEach(function(h) {
var req = http.get({
port: port,
headers: h
}); });
var sawUpgrade = false;
req.on('upgrade', common.mustCall(function(res, socket, upgradeHead) { req.on('upgrade', common.mustCall(function(res, socket, upgradeHead) {
sawUpgrade = true;
var recvData = upgradeHead; var recvData = upgradeHead;
socket.on('data', function(d) { socket.on('data', function(d) {
recvData += d; recvData += d;
@ -45,12 +58,19 @@ srv.listen(0, '127.0.0.1', common.mustCall(function() {
})); }));
console.log(res.headers); console.log(res.headers);
var expectedHeaders = {'hello': 'world', var expectedHeaders = {
'connection': 'upgrade', hello: 'world',
'upgrade': 'websocket' }; connection: 'upgrade',
upgrade: 'websocket'
};
assert.deepStrictEqual(expectedHeaders, res.headers); assert.deepStrictEqual(expectedHeaders, res.headers);
socket.end(); socket.end();
if (--left == 0)
srv.close(); srv.close();
})); }));
req.on('close', common.mustCall(function() {
assert.strictEqual(sawUpgrade, true);
}));
});
})); }));

Loading…
Cancel
Save