Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

http: fix parsing of binary upgrade response body #17806

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 9 additions & 15 deletions lib/_http_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
var socket = this.socket;
var req = socket._httpMessage;


// propagate "domain" setting...
if (req.domain && !res.domain) {
debug('setting "res.domain"');
Expand All @@ -471,29 +470,22 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
// We already have a response object, this means the server
// sent a double response.
socket.destroy();
return;
return 0; // No special treatment.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before anyone asks - yes, these could be constants, please suggest good names. :-)

(They don't have names in http_parser either though.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just made these up on the spot. Feel free to ignore. How about kSkipBodyAsUpgrade, kSkipBodyNoUpgrade... and I don't know for the 0 case.

}
req.res = res;

// Responses to CONNECT request is handled as Upgrade.
if (req.method === 'CONNECT') {
const method = req.method;
if (method === 'CONNECT') {
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);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed because not very useful and slightly misleading - this is not inside the agent.


if (res.statusCode === 100) {
// restart the parser, as this is a continue message.
req.res = null; // Clear res so that we don't hit double-responses.
req.emit('continue');
return true;
return 1; // Skip body but don't treat as Upgrade.
}

if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
Expand All @@ -503,7 +495,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
req.shouldKeepAlive = false;
}


DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
LTTNG_HTTP_CLIENT_RESPONSE(socket, req);
COUNTER_HTTP_CLIENT_RESPONSE();
Expand All @@ -521,7 +512,10 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
if (!handled)
res._dump();

return isHeadResponse;
if (method === 'HEAD')
return 1; // Skip body but don't treat as Upgrade.

return 0; // No special treatment.
}

// client
Expand Down
15 changes: 3 additions & 12 deletions lib/_http_common.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,19 +106,10 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,

parser.incoming.upgrade = upgrade;

var skipBody = 0; // response to HEAD or CONNECT
if (upgrade)
return 2; // Skip body and treat as Upgrade.

if (!upgrade) {
// 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.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slightly wrong comment, removed.

skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive);
}

if (typeof skipBody !== 'number')
return skipBody ? 1 : 0;
else
return skipBody;
return parser.onIncoming(parser.incoming, shouldKeepAlive);
}

// XXX This is a mess.
Expand Down
2 changes: 1 addition & 1 deletion lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
} else {
server.emit('request', req, res);
}
return false; // Not a HEAD response. (Not even a response!)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bogus comment, removed.

return 0; // No special treatment.
}

function resetSocketTimeout(server, socket, state) {
Expand Down
28 changes: 28 additions & 0 deletions test/parallel/test-http-upgrade-binary.js
Original file line number Diff line number Diff line change
@@ -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();
}));
}));