http: do not emit `upgrade` on advertisement
authorFedor Indutny <fedor@indutny.com>
Thu, 17 Dec 2015 22:23:46 +0000 (17:23 -0500)
committerMyles Borins <mborins@us.ibm.com>
Wed, 2 Mar 2016 22:01:11 +0000 (14:01 -0800)
Do not emit `upgrade` if the server is just advertising its protocols
support as per RFC 7230 Section 6.7.

    A server MAY send an Upgrade header field in any other response
    to advertise that it implements support for upgrading to the
    listed protocols, in order of descending preference, when
    appropriate for a future request.

Fix: https://github.com/nodejs/node/issues/4334
PR-URL: https://github.com/nodejs/node/pull/4337
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
lib/_http_client.js
lib/_http_common.js
test/parallel/test-http-upgrade-advertise.js [new file with mode: 0644]
test/parallel/test-http-upgrade-agent.js
test/parallel/test-http-upgrade-client.js

index 108b388..04740d5 100644 (file)
@@ -465,6 +465,7 @@ function tickOnSocket(req, socket) {
   parser.reinitialize(HTTPParser.RESPONSE);
   parser.socket = socket;
   parser.incoming = null;
+  parser.outgoing = req;
   req.parser = parser;
 
   socket.parser = parser;
index 5c24180..66d4d1f 100644 (file)
@@ -77,6 +77,20 @@ function parserOnHeadersComplete(versionMajor, versionMinor, headers, method,
     parser.incoming.statusMessage = statusMessage;
   }
 
+  // The client made non-upgrade request, and server is just advertising
+  // supported protocols.
+  //
+  // 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;
+  }
+
   parser.incoming.upgrade = upgrade;
 
   var skipBody = false; // response to HEAD or CONNECT
@@ -142,6 +156,10 @@ var parsers = new FreeList('parsers', 1000, function() {
   parser._url = '';
   parser._consumed = false;
 
+  parser.socket = null;
+  parser.incoming = null;
+  parser.outgoing = null;
+
   // Only called in the slow case where slow means
   // that the request headers were either fragmented
   // across multiple TCP packets or too large to be
@@ -175,6 +193,7 @@ function freeParser(parser, req, socket) {
       parser.socket.parser = null;
     parser.socket = null;
     parser.incoming = null;
+    parser.outgoing = null;
     if (parsers.free(parser) === false)
       parser.close();
     parser = null;
diff --git a/test/parallel/test-http-upgrade-advertise.js b/test/parallel/test-http-upgrade-advertise.js
new file mode 100644 (file)
index 0000000..fbc183f
--- /dev/null
@@ -0,0 +1,54 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert');
+const http = require('http');
+
+const tests = [
+  { headers: {}, expected: 'regular' },
+  { headers: { upgrade: 'h2c' }, expected: 'regular' },
+  { headers: { connection: 'upgrade' }, expected: 'regular' },
+  { headers: { connection: 'upgrade', upgrade: 'h2c' }, expected: 'upgrade' }
+];
+
+function fire() {
+  if (tests.length === 0)
+    return server.close();
+
+  const test = tests.shift();
+
+  const done = common.mustCall(function done(result) {
+    assert.equal(result, test.expected);
+
+    fire();
+  });
+
+  const req = http.request({
+    port: common.PORT,
+    path: '/',
+    headers: test.headers
+  }, function onResponse(res) {
+    res.resume();
+    done('regular');
+  });
+
+  req.on('upgrade', function onUpgrade(res, socket) {
+    socket.destroy();
+    done('upgrade');
+  });
+
+  req.end();
+}
+
+const server = http.createServer(function(req, res) {
+  res.writeHead(200, {
+    Connection: 'upgrade, keep-alive',
+    Upgrade: 'h2c'
+  });
+  res.end('hello world');
+}).on('upgrade', function(req, socket) {
+  socket.end('HTTP/1.1 101 Switching protocols\r\n' +
+             'Connection: upgrade\r\n' +
+             'Upgrade: h2c\r\n\r\n' +
+             'ohai');
+}).listen(common.PORT, fire);
index 83f783a..7590354 100644 (file)
@@ -36,6 +36,7 @@ srv.listen(common.PORT, '127.0.0.1', function() {
     port: common.PORT,
     host: '127.0.0.1',
     headers: {
+      'connection': 'upgrade',
       'upgrade': 'websocket'
     }
   };
index a86b0e3..a45c06b 100644 (file)
@@ -32,7 +32,13 @@ var gotUpgrade = false;
 
 srv.listen(common.PORT, '127.0.0.1', function() {
 
-  var req = http.get({ port: common.PORT });
+  var req = http.get({
+    port: common.PORT,
+    headers: {
+      connection: 'upgrade',
+      upgrade: 'websocket'
+    }
+  });
   req.on('upgrade', function(res, socket, upgradeHead) {
     var recvData = upgradeHead;
     socket.on('data', function(d) {