Skip to content

Commit 1556228

Browse files
bnoordhuisevanlucas
authored andcommitted
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: #17789 PR-URL: #17806 Fixes: #17789 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Luigi Pinca <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]> Reviewed-By: Richard Lau <[email protected]> Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Ruben Bridgewater <[email protected]>
1 parent 761f26e commit 1556228

File tree

4 files changed

+41
-28
lines changed

4 files changed

+41
-28
lines changed

lib/_http_client.js

+9-15
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
490490
var socket = this.socket;
491491
var req = socket._httpMessage;
492492

493-
494493
// propagate "domain" setting...
495494
if (req.domain && !res.domain) {
496495
debug('setting "res.domain"');
@@ -503,29 +502,22 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
503502
// We already have a response object, this means the server
504503
// sent a double response.
505504
socket.destroy();
506-
return;
505+
return 0; // No special treatment.
507506
}
508507
req.res = res;
509508

510509
// Responses to CONNECT request is handled as Upgrade.
511-
if (req.method === 'CONNECT') {
510+
const method = req.method;
511+
if (method === 'CONNECT') {
512512
res.upgrade = true;
513-
return 2; // skip body, and the rest
513+
return 2; // Skip body and treat as Upgrade.
514514
}
515515

516-
// Responses to HEAD requests are crazy.
517-
// HEAD responses aren't allowed to have an entity-body
518-
// but *can* have a content-length which actually corresponds
519-
// to the content-length of the entity-body had the request
520-
// been a GET.
521-
var isHeadResponse = req.method === 'HEAD';
522-
debug('AGENT isHeadResponse', isHeadResponse);
523-
524516
if (res.statusCode === 100) {
525517
// restart the parser, as this is a continue message.
526518
req.res = null; // Clear res so that we don't hit double-responses.
527519
req.emit('continue');
528-
return true;
520+
return 1; // Skip body but don't treat as Upgrade.
529521
}
530522

531523
if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
@@ -535,7 +527,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
535527
req.shouldKeepAlive = false;
536528
}
537529

538-
539530
DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
540531
LTTNG_HTTP_CLIENT_RESPONSE(socket, req);
541532
COUNTER_HTTP_CLIENT_RESPONSE();
@@ -553,7 +544,10 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
553544
if (!handled)
554545
res._dump();
555546

556-
return isHeadResponse;
547+
if (method === 'HEAD')
548+
return 1; // Skip body but don't treat as Upgrade.
549+
550+
return 0; // No special treatment.
557551
}
558552

559553
// client

lib/_http_common.js

+3-12
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,10 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
106106

107107
parser.incoming.upgrade = upgrade;
108108

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

111-
if (!upgrade) {
112-
// For upgraded connections and CONNECT method request, we'll emit this
113-
// after parser.execute so that we can capture the first part of the new
114-
// protocol.
115-
skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive);
116-
}
117-
118-
if (typeof skipBody !== 'number')
119-
return skipBody ? 1 : 0;
120-
else
121-
return skipBody;
112+
return parser.onIncoming(parser.incoming, shouldKeepAlive);
122113
}
123114

124115
// XXX This is a mess.

lib/_http_server.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
617617
} else {
618618
server.emit('request', req, res);
619619
}
620-
return false; // Not a HEAD response. (Not even a response!)
620+
return 0; // No special treatment.
621621
}
622622

623623
function resetSocketTimeout(server, socket, state) {
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
const { mustCall } = require('../common');
3+
const assert = require('assert');
4+
const http = require('http');
5+
const net = require('net');
6+
7+
// https://github.com/nodejs/node/issues/17789 - a connection upgrade response
8+
// that has a Transfer-Encoding header and a body whose first byte is > 127
9+
// triggers a bug where said byte is skipped over.
10+
net.createServer(mustCall(function(conn) {
11+
conn.write('HTTP/1.1 101 Switching Protocols\r\n' +
12+
'Connection: upgrade\r\n' +
13+
'Transfer-Encoding: chunked\r\n' +
14+
'Upgrade: websocket\r\n' +
15+
'\r\n' +
16+
'\u0080', 'latin1');
17+
this.close();
18+
})).listen(0, mustCall(function() {
19+
http.get({
20+
host: this.address().host,
21+
port: this.address().port,
22+
headers: { 'Connection': 'upgrade', 'Upgrade': 'websocket' },
23+
}).on('upgrade', mustCall((res, conn, head) => {
24+
assert.strictEqual(head.length, 1);
25+
assert.strictEqual(head[0], 128);
26+
conn.destroy();
27+
}));
28+
}));

0 commit comments

Comments
 (0)