http: fix parsing of binary upgrade response body
Fix a bug where a connection upgrade response with a Transfer-Encoding header and a body whose first byte is > 127 causes said byte to be dropped on the floor when passing the remainder of the message to the 'upgrade' event listeners. Fixes: https://github.com/nodejs/node/issues/17789 PR-URL: https://github.com/nodejs/node/pull/17806 Fixes: https://github.com/nodejs/node/issues/17789 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com> Reviewed-By: Richard Lau <riclau@uk.ibm.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This commit is contained in:
parent
e1c29f2c52
commit
de4600ea2b
@ -458,7 +458,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
|
|||||||
var socket = this.socket;
|
var socket = this.socket;
|
||||||
var req = socket._httpMessage;
|
var req = socket._httpMessage;
|
||||||
|
|
||||||
|
|
||||||
// propagate "domain" setting...
|
// propagate "domain" setting...
|
||||||
if (req.domain && !res.domain) {
|
if (req.domain && !res.domain) {
|
||||||
debug('setting "res.domain"');
|
debug('setting "res.domain"');
|
||||||
@ -471,29 +470,22 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
|
|||||||
// We already have a response object, this means the server
|
// We already have a response object, this means the server
|
||||||
// sent a double response.
|
// sent a double response.
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
return;
|
return 0; // No special treatment.
|
||||||
}
|
}
|
||||||
req.res = res;
|
req.res = res;
|
||||||
|
|
||||||
// Responses to CONNECT request is handled as Upgrade.
|
// Responses to CONNECT request is handled as Upgrade.
|
||||||
if (req.method === 'CONNECT') {
|
const method = req.method;
|
||||||
|
if (method === 'CONNECT') {
|
||||||
res.upgrade = true;
|
res.upgrade = true;
|
||||||
return 2; // skip body, and the rest
|
return 2; // Skip body and treat as Upgrade.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Responses to HEAD requests are crazy.
|
|
||||||
// HEAD responses aren't allowed to have an entity-body
|
|
||||||
// but *can* have a content-length which actually corresponds
|
|
||||||
// to the content-length of the entity-body had the request
|
|
||||||
// been a GET.
|
|
||||||
var isHeadResponse = req.method === 'HEAD';
|
|
||||||
debug('AGENT isHeadResponse', isHeadResponse);
|
|
||||||
|
|
||||||
if (res.statusCode === 100) {
|
if (res.statusCode === 100) {
|
||||||
// restart the parser, as this is a continue message.
|
// restart the parser, as this is a continue message.
|
||||||
req.res = null; // Clear res so that we don't hit double-responses.
|
req.res = null; // Clear res so that we don't hit double-responses.
|
||||||
req.emit('continue');
|
req.emit('continue');
|
||||||
return true;
|
return 1; // Skip body but don't treat as Upgrade.
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
|
if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
|
||||||
@ -503,7 +495,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
|
|||||||
req.shouldKeepAlive = false;
|
req.shouldKeepAlive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
|
DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
|
||||||
LTTNG_HTTP_CLIENT_RESPONSE(socket, req);
|
LTTNG_HTTP_CLIENT_RESPONSE(socket, req);
|
||||||
COUNTER_HTTP_CLIENT_RESPONSE();
|
COUNTER_HTTP_CLIENT_RESPONSE();
|
||||||
@ -521,7 +512,10 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
|
|||||||
if (!handled)
|
if (!handled)
|
||||||
res._dump();
|
res._dump();
|
||||||
|
|
||||||
return isHeadResponse;
|
if (method === 'HEAD')
|
||||||
|
return 1; // Skip body but don't treat as Upgrade.
|
||||||
|
|
||||||
|
return 0; // No special treatment.
|
||||||
}
|
}
|
||||||
|
|
||||||
// client
|
// client
|
||||||
|
@ -106,19 +106,10 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
|
|||||||
|
|
||||||
parser.incoming.upgrade = upgrade;
|
parser.incoming.upgrade = upgrade;
|
||||||
|
|
||||||
var skipBody = 0; // response to HEAD or CONNECT
|
if (upgrade)
|
||||||
|
return 2; // Skip body and treat as Upgrade.
|
||||||
|
|
||||||
if (!upgrade) {
|
return parser.onIncoming(parser.incoming, shouldKeepAlive);
|
||||||
// For upgraded connections and CONNECT method request, we'll emit this
|
|
||||||
// after parser.execute so that we can capture the first part of the new
|
|
||||||
// protocol.
|
|
||||||
skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof skipBody !== 'number')
|
|
||||||
return skipBody ? 1 : 0;
|
|
||||||
else
|
|
||||||
return skipBody;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// XXX This is a mess.
|
// XXX This is a mess.
|
||||||
|
@ -627,7 +627,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
|
|||||||
} else {
|
} else {
|
||||||
server.emit('request', req, res);
|
server.emit('request', req, res);
|
||||||
}
|
}
|
||||||
return false; // Not a HEAD response. (Not even a response!)
|
return 0; // No special treatment.
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetSocketTimeout(server, socket, state) {
|
function resetSocketTimeout(server, socket, state) {
|
||||||
|
28
test/parallel/test-http-upgrade-binary.js
Normal file
28
test/parallel/test-http-upgrade-binary.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
'use strict';
|
||||||
|
const { mustCall } = require('../common');
|
||||||
|
const assert = require('assert');
|
||||||
|
const http = require('http');
|
||||||
|
const net = require('net');
|
||||||
|
|
||||||
|
// https://github.com/nodejs/node/issues/17789 - a connection upgrade response
|
||||||
|
// that has a Transfer-Encoding header and a body whose first byte is > 127
|
||||||
|
// triggers a bug where said byte is skipped over.
|
||||||
|
net.createServer(mustCall(function(conn) {
|
||||||
|
conn.write('HTTP/1.1 101 Switching Protocols\r\n' +
|
||||||
|
'Connection: upgrade\r\n' +
|
||||||
|
'Transfer-Encoding: chunked\r\n' +
|
||||||
|
'Upgrade: websocket\r\n' +
|
||||||
|
'\r\n' +
|
||||||
|
'\u0080', 'latin1');
|
||||||
|
this.close();
|
||||||
|
})).listen(0, mustCall(function() {
|
||||||
|
http.get({
|
||||||
|
host: this.address().host,
|
||||||
|
port: this.address().port,
|
||||||
|
headers: { 'Connection': 'upgrade', 'Upgrade': 'websocket' },
|
||||||
|
}).on('upgrade', mustCall((res, conn, head) => {
|
||||||
|
assert.strictEqual(head.length, 1);
|
||||||
|
assert.strictEqual(head[0], 128);
|
||||||
|
conn.destroy();
|
||||||
|
}));
|
||||||
|
}));
|
Loading…
x
Reference in New Issue
Block a user