Skip to content

Commit d2a884e

Browse files
bnoordhuisMylesBorins
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 c897815 commit d2a884e

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
@@ -493,7 +493,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
493493
var socket = this.socket;
494494
var req = socket._httpMessage;
495495

496-
497496
// propagate "domain" setting...
498497
if (req.domain && !res.domain) {
499498
debug('setting "res.domain"');
@@ -506,29 +505,22 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
506505
// We already have a response object, this means the server
507506
// sent a double response.
508507
socket.destroy();
509-
return;
508+
return 0; // No special treatment.
510509
}
511510
req.res = res;
512511

513512
// Responses to CONNECT request is handled as Upgrade.
514-
if (req.method === 'CONNECT') {
513+
const method = req.method;
514+
if (method === 'CONNECT') {
515515
res.upgrade = true;
516-
return 2; // skip body, and the rest
516+
return 2; // Skip body and treat as Upgrade.
517517
}
518518

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

534526
if (req.shouldKeepAlive && !shouldKeepAlive && !req.upgradeOrConnect) {
@@ -538,7 +530,6 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
538530
req.shouldKeepAlive = false;
539531
}
540532

541-
542533
DTRACE_HTTP_CLIENT_RESPONSE(socket, req);
543534
LTTNG_HTTP_CLIENT_RESPONSE(socket, req);
544535
COUNTER_HTTP_CLIENT_RESPONSE();
@@ -556,7 +547,10 @@ function parserOnIncomingClient(res, shouldKeepAlive) {
556547
if (!handled)
557548
res._dump();
558549

559-
return isHeadResponse;
550+
if (method === 'HEAD')
551+
return 1; // Skip body but don't treat as Upgrade.
552+
553+
return 0; // No special treatment.
560554
}
561555

562556
// 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
@@ -618,7 +618,7 @@ function parserOnIncoming(server, socket, state, req, keepAlive) {
618618
} else {
619619
server.emit('request', req, res);
620620
}
621-
return false; // Not a HEAD response. (Not even a response!)
621+
return 0; // No special treatment.
622622
}
623623

624624
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)