Bug 1136361 - update node-http2 on ci. rs=mcmanus
authorNicholas Hurley <hurley@todesschaf.org>
Tue, 24 Feb 2015 14:01:10 -0800
changeset 247167 b56e18c9dbfc4ce4785c809e7eec5a835f4d325b
parent 247166 9f70f885338c0726de548d26b962e8e627875ea0
child 247168 dcb0abbfa4235ee531cddbed5a38a363cbcf1844
push idunknown
push userunknown
push dateunknown
reviewersmcmanus
bugs1136361
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1136361 - update node-http2 on ci. rs=mcmanus
netwerk/test/unit/test_http2.js
testing/xpcshell/moz-http2/moz-http2.js
testing/xpcshell/node-http2/HISTORY.md
testing/xpcshell/node-http2/example/server.js
testing/xpcshell/node-http2/lib/protocol/framer.js
testing/xpcshell/node-http2/lib/protocol/index.js
testing/xpcshell/node-http2/package.json
testing/xpcshell/node-http2/test/framer.js
--- a/netwerk/test/unit/test_http2.js
+++ b/netwerk/test/unit/test_http2.js
@@ -576,17 +576,17 @@ var altsvcClientListener2 = {
     }
   }
 };
 
 function altsvcHttp1Server(metadata, response) {
   response.setStatusLine(metadata.httpVersion, 200, "OK");
   response.setHeader("Content-Type", "text/plain", false);
   response.setHeader("Connection", "close", false);
-  response.setHeader("Alt-Svc", 'h2-16=":' + serverPort + '"', false);
+  response.setHeader("Alt-Svc", 'h2=":' + serverPort + '"', false);
   var body = "this is where a cool kid would write something neat.\n";
   response.bodyOutputStream.write(body, body.length);
 }
 
 function altsvcHttp1Server2(metadata, response) {
 // this server should never be used thanks to an alt svc frame from the
 // h2 server.. but in case of some async lag in setting the alt svc route
 // up we have it.
@@ -854,28 +854,30 @@ function addCertOverride(host, port, bit
     // This will fail since the server is not trusted yet
   }
 }
 
 var prefs;
 var spdypref;
 var spdy3pref;
 var spdypush;
+var http2draftpref;
 var http2pref;
 var tlspref;
 var altsvcpref1;
 var altsvcpref2;
 var loadGroup;
 var serverPort;
 
 function resetPrefs() {
   prefs.setBoolPref("network.http.spdy.enabled", spdypref);
   prefs.setBoolPref("network.http.spdy.enabled.v3-1", spdy3pref);
   prefs.setBoolPref("network.http.spdy.allow-push", spdypush);
-  prefs.setBoolPref("network.http.spdy.enabled.http2draft", http2pref);
+  prefs.setBoolPref("network.http.spdy.enabled.http2draft", http2draftpref);
+  prefs.setBoolPref("network.http.spdy.enabled.http2", http2pref);
   prefs.setBoolPref("network.http.spdy.enforce-tls-profile", tlspref);
   prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1);
   prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2);
 }
 
 function run_test() {
   var env = Cc["@mozilla.org/process/environment;1"].getService(Ci.nsIEnvironment);
   serverPort = env.get("MOZHTTP2-PORT");
@@ -894,25 +896,27 @@ function run_test() {
                   Ci.nsICertOverrideService.ERROR_TIME);
 
   prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
 
   // Enable all versions of spdy to see that we auto negotiate http/2
   spdypref = prefs.getBoolPref("network.http.spdy.enabled");
   spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3-1");
   spdypush = prefs.getBoolPref("network.http.spdy.allow-push");
-  http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2draft");
+  http2draftpref = prefs.getBoolPref("network.http.spdy.enabled.http2draft");
+  http2pref = prefs.getBoolPref("network.http.spdy.enabled.http2");
   tlspref = prefs.getBoolPref("network.http.spdy.enforce-tls-profile");
   altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled");
   altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true);
 
   prefs.setBoolPref("network.http.spdy.enabled", true);
   prefs.setBoolPref("network.http.spdy.enabled.v3-1", true);
   prefs.setBoolPref("network.http.spdy.allow-push", true);
   prefs.setBoolPref("network.http.spdy.enabled.http2draft", true);
+  prefs.setBoolPref("network.http.spdy.enabled.http2", true);
   prefs.setBoolPref("network.http.spdy.enforce-tls-profile", false);
   prefs.setBoolPref("network.http.altsvc.enabled", true);
   prefs.setBoolPref("network.http.altsvc.oe", true);
 
   loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup);
 
   httpserv = new HttpServer();
   httpserv.registerPathHandler("/altsvc1", altsvcHttp1Server);
--- a/testing/xpcshell/moz-http2/moz-http2.js
+++ b/testing/xpcshell/moz-http2/moz-http2.js
@@ -377,17 +377,17 @@ function handleRequest(req, res) {
       req.scheme != "http" ||
       req.headers['alt-used'] != ("localhost:" + serverPort)) {
       res.setHeader('Connection', 'close');
       res.writeHead(400);
       res.end("WHAT?");
       return;
    }
    // test the alt svc frame for use with altsvc2
-   res.altsvc("localhost", serverPort, "h2-16", 3600, req.headers['x-redirect-origin']);
+   res.altsvc("localhost", serverPort, "h2", 3600, req.headers['x-redirect-origin']);
   }
 
   else if (u.pathname === "/altsvc2") {
     if (req.httpVersionMajor != 2 ||
       req.scheme != "http" ||
       req.headers['alt-used'] != ("localhost:" + serverPort)) {
       res.setHeader('Connection', 'close');
       res.writeHead(400);
--- a/testing/xpcshell/node-http2/HISTORY.md
+++ b/testing/xpcshell/node-http2/HISTORY.md
@@ -1,11 +1,22 @@
 Version history
 ===============
 
+### 3.2.0 (2015-02-19) ###
+
+* Update ALPN token to final RFC version (h2).
+* Update altsvc implementation to draft 06: [draft-ietf-httpbis-alt-svc-06]
+
+[draft-ietf-httpbis-altsvc-06]: http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06
+
+### 3.1.2 (2015-02-17) ###
+
+* Update the example server to have a safe push example.
+
 ### 3.1.1 (2015-01-29) ###
 
 * Bugfix release.
 * Fixes an issue sending a push promise that is large enough to fill the frame (#93).
 
 ### 3.1.0 (2014-12-11) ###
 
 * Upgrade to the latest draft: [draft-ietf-httpbis-http2-16]
--- a/testing/xpcshell/node-http2/example/server.js
+++ b/testing/xpcshell/node-http2/example/server.js
@@ -7,30 +7,30 @@ var cachedFile = fs.readFileSync(path.jo
 var cachedUrl = '/server.js';
 
 // The callback to handle requests
 function onRequest(request, response) {
   var filename = path.join(__dirname, request.url);
 
   // Serving server.js from cache. Useful for microbenchmarks.
   if (request.url === cachedUrl) {
+    if (response.push) {
+      // Also push down the client js, since it's possible if the requester wants
+      // one, they want both.
+      var push = response.push('/client.js');
+      push.writeHead(200);
+      fs.createReadStream(path.join(__dirname, '/client.js')).pipe(push);
+    }
     response.end(cachedFile);
   }
 
   // Reading file from disk if it exists and is safe.
   else if ((filename.indexOf(__dirname) === 0) && fs.existsSync(filename) && fs.statSync(filename).isFile()) {
     response.writeHead('200');
 
-    // If they download the certificate, push the private key too, they might need it.
-    if (response.push && request.url === '/localhost.crt') {
-      var push = response.push('/localhost.key');
-      push.writeHead(200);
-      fs.createReadStream(path.join(__dirname, '/localhost.key')).pipe(push);
-    }
-
     fs.createReadStream(filename).pipe(response);
   }
 
   // Otherwise responding with 404.
   else {
     response.writeHead('404');
     response.end();
   }
--- a/testing/xpcshell/node-http2/lib/protocol/framer.js
+++ b/testing/xpcshell/node-http2/lib/protocol/framer.js
@@ -795,49 +795,228 @@ Deserializer.CONTINUATION = function rea
 frameTypes[0xA] = 'ALTSVC';
 
 frameFlags.ALTSVC = [];
 
 //     0                   1                   2                   3
 //     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 //    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 //    |         Origin-Len (16)       | Origin? (*)                 ...
-//    +-------------------------------+-------------------------------+
+//    +-------------------------------+----------------+--------------+
 //    |                   Alt-Svc-Field-Value (*)                   ...
 //    +---------------------------------------------------------------+
 //
 // The ALTSVC frame contains the following fields:
 //
-// Origin-Len: An unsinged 16 bit integer indicating the length of the origin
-//    field. Must be empty for non zero stream id
+// Origin-Len: An unsigned, 16-bit integer indicating the length, in
+//    octets, of the Origin field.
 //
-// Origin: origin this directive applies to. If empty it applies to the
-//    same origin as the stream with the same id
+// Origin: An OPTIONAL sequence of characters containing ASCII
+//    serialisation of an origin ([RFC6454](http://tools.ietf.org/html/rfc6454),
+//    Section 6.2) that the alternate service is applicable to.
 //
-// Alt-Svc-Field-Value: an ascaii string corresponding to the HTTP response
-//    header field value for Alt-Svc
+// Alt-Svc-Field-Value: A sequence of octets (length determined by
+//    subtracting the length of all preceding fields from the frame
+//    length) containing a value identical to the Alt-Svc field value
+//    defined in (Section 3)[http://tools.ietf.org/html/draft-ietf-httpbis-alt-svc-06#section-3]
+//    (ABNF production "Alt-Svc").
 
 typeSpecificAttributes.ALTSVC = ['maxAge', 'port', 'protocolID', 'host',
                                  'origin'];
 
+function istchar(c) {
+  return ('!#$&\'*+-.^_`|~1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.indexOf(c) > -1);
+}
+
+function hexencode(s) {
+  var t = '';
+  for (var i = 0; i < s.length; i++) {
+    if (!istchar(s[i])) {
+      t += '%';
+      t += new Buffer(s[i]).toString('hex');
+    } else {
+      t += s[i];
+    }
+  }
+  return t;
+}
+
 Serializer.ALTSVC = function writeAltSvc(frame, buffers) {
-  var hdr = frame.protocolID + "=\"" + frame.host + ":" + frame.port + "\"";
-  if (frame.maxAge) {
-   hdr += "; ma=" + frame.maxAge;
-  }
-  
-  buffer = new Buffer(2);
+  var buffer = new Buffer(2);
   buffer.writeUInt16BE(frame.origin.length, 0);
   buffers.push(buffer);
   buffers.push(new Buffer(frame.origin, 'ascii'));
-  buffers.push(new Buffer(hdr, 'ascii'));
+
+  var fieldValue = hexencode(frame.protocolID) + '="' + frame.host + ':' + frame.port + '"';
+  if (frame.maxAge !== 86400) { // 86400 is the default
+    fieldValue += "; ma=" + frame.maxAge;
+  }
+
+  buffers.push(new Buffer(fieldValue, 'ascii'));
 };
 
+function stripquotes(s) {
+  var start = 0;
+  var end = s.length;
+  while ((start < end) && (s[start] === '"')) {
+    start++;
+  }
+  while ((end > start) && (s[end - 1] === '"')) {
+    end--;
+  }
+  if (start >= end) {
+    return "";
+  }
+  return s.substring(start, end);
+}
+
+function splitNameValue(nvpair) {
+  var eq = -1;
+  var inQuotes = false;
+
+  for (var i = 0; i < nvpair.length; i++) {
+    if (nvpair[i] === '"') {
+      inQuotes = !inQuotes;
+      continue;
+    }
+    if (inQuotes) {
+      continue;
+    }
+    if (nvpair[i] === '=') {
+      eq = i;
+      break;
+    }
+  }
+
+  if (eq === -1) {
+    return {'name': nvpair, 'value': null};
+  }
+
+  var name = stripquotes(nvpair.substring(0, eq).trim());
+  var value = stripquotes(nvpair.substring(eq + 1).trim());
+  return {'name': name, 'value': value};
+}
+
+function splitHeaderParameters(hv) {
+  return parseHeaderValue(hv, ';', splitNameValue);
+}
+
+function parseHeaderValue(hv, separator, callback) {
+  var start = 0;
+  var inQuotes = false;
+  var values = [];
+
+  for (var i = 0; i < hv.length; i++) {
+    if (hv[i] === '"') {
+      inQuotes = !inQuotes;
+      continue;
+    }
+    if (inQuotes) {
+      // Just skip this
+      continue;
+    }
+    if (hv[i] === separator) {
+      var newValue = hv.substring(start, i).trim();
+      if (newValue.length > 0) {
+        newValue = callback(newValue);
+        values.push(newValue);
+      }
+      start = i + 1;
+    }
+  }
+
+  var newValue = hv.substring(start).trim();
+  if (newValue.length > 0) {
+    newValue = callback(newValue);
+    values.push(newValue);
+  }
+
+  return values;
+}
+
+function rsplit(s, delim, count) {
+  var nsplits = 0;
+  var end = s.length;
+  var rval = [];
+  for (var i = s.length - 1; i >= 0; i--) {
+    if (s[i] === delim) {
+      var t = s.substring(i + 1, end);
+      end = i;
+      rval.unshift(t);
+      nsplits++;
+      if (nsplits === count) {
+        break;
+      }
+    }
+  }
+  if (end !== 0) {
+    rval.unshift(s.substring(0, end));
+  }
+  return rval;
+}
+
+function ishex(c) {
+  return ('0123456789ABCDEFabcdef'.indexOf(c) > -1);
+}
+
+function unescape(s) {
+  var i = 0;
+  var t = '';
+  while (i < s.length) {
+    if (s[i] != '%' || !ishex(s[i + 1]) || !ishex(s[i + 2])) {
+      t += s[i];
+    } else {
+      ++i;
+      var hexvalue = '';
+      if (i < s.length) {
+        hexvalue += s[i];
+        ++i;
+      }
+      if (i < s.length) {
+        hexvalue += s[i];
+      }
+      if (hexvalue.length > 0) {
+        t += new Buffer(hexvalue, 'hex').toString();
+      } else {
+        t += '%';
+      }
+    }
+
+    ++i;
+  }
+  return t;
+}
+
 Deserializer.ALTSVC = function readAltSvc(buffer, frame) {
-  // todo
+  var originLength = buffer.readUInt16BE(0);
+  frame.origin = buffer.toString('ascii', 2, 2 + originLength);
+  var fieldValue = buffer.toString('ascii', 2 + originLength);
+  var values = parseHeaderValue(fieldValue, ',', splitHeaderParameters);
+  if (values.length > 1) {
+    // TODO - warn that we only use one here
+  }
+  if (values.length === 0) {
+    // Well that's a malformed frame. Just ignore it.
+    return;
+  }
+
+  var chosenAltSvc = values[0];
+  frame.maxAge = 86400; // Default
+  for (var i = 0; i < chosenAltSvc.length; i++) {
+    if (i === 0) {
+      // This corresponds to the protocolID="<host>:<port>" item
+      frame.protocolID = unescape(chosenAltSvc[i].name);
+      var hostport = rsplit(chosenAltSvc[i].value, ':', 1);
+      frame.host = hostport[0];
+      frame.port = parseInt(hostport[1], 10);
+    } else if (chosenAltSvc[i].name == 'ma') {
+      frame.maxAge = parseInt(chosenAltSvc[i].value, 10);
+    }
+    // Otherwise, we just ignore this
+  }
 };
 
 // BLOCKED
 // ------------------------------------------------------------
 //
 // The BLOCKED frame (type=0xB) indicates that the sender is unable to send data
 // due to a closed flow control window.
 //
--- a/testing/xpcshell/node-http2/lib/protocol/index.js
+++ b/testing/xpcshell/node-http2/lib/protocol/index.js
@@ -32,17 +32,17 @@
 // [http2-connheader]:    http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-3.5
 // [http2-stream]:        http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5
 // [http2-streamstate]:   http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-5.1
 // [node]:                http://nodejs.org/
 // [node-stream]:         http://nodejs.org/api/stream.html
 // [node-https]:          http://nodejs.org/api/https.html
 // [node-http]:           http://nodejs.org/api/http.html
 
-exports.VERSION = 'h2-16';
+exports.VERSION = 'h2';
 
 exports.Endpoint = require('./endpoint').Endpoint;
 
 /* Bunyan serializers exported by submodules that are worth adding when creating a logger. */
 exports.serializers = {};
 var modules = ['./framer', './compressor', './flow', './connection', './stream', './endpoint'];
 modules.map(require).forEach(function(module) {
   for (var name in module.serializers) {
--- a/testing/xpcshell/node-http2/package.json
+++ b/testing/xpcshell/node-http2/package.json
@@ -1,11 +1,11 @@
 {
   "name": "http2",
-  "version": "3.1.1",
+  "version": "3.2.0",
   "description": "An HTTP/2 client and server implementation",
   "main": "lib/index.js",
   "engines" : {
     "node" : ">=0.10.19"
   },
   "devDependencies": {
     "istanbul": "*",
     "chai": "*",
--- a/testing/xpcshell/node-http2/test/framer.js
+++ b/testing/xpcshell/node-http2/test/framer.js
@@ -10,17 +10,18 @@ var frame_types = {
   HEADERS:       ['priority_information', 'data'],
   PRIORITY:      ['priority_information'],
   RST_STREAM:    ['error'],
   SETTINGS:      ['settings'],
   PUSH_PROMISE:  ['promised_stream', 'data'],
   PING:          ['data'],
   GOAWAY:        ['last_stream', 'error'],
   WINDOW_UPDATE: ['window_size'],
-  CONTINUATION:  ['data']
+  CONTINUATION:  ['data'],
+  ALTSVC:        ['protocolID', 'host', 'port', 'origin', 'maxAge']
 };
 
 var test_frames = [{
   frame: {
     type: 'DATA',
     flags: { END_STREAM: false, RESERVED2: false, RESERVED4: false,
              PADDED: false },
     stream: 10,
@@ -171,47 +172,44 @@ var test_frames = [{
     type: 'CONTINUATION',
     flags: { RESERVED1: false, RESERVED2: false, END_HEADERS: true },
     stream: 10,
 
     data: new Buffer('12345678', 'hex')
   },
   // length + type + flags + stream +   content
   buffer: new Buffer('000004' + '09' + '04' + '0000000A' +   '12345678', 'hex')
-}, 
-// need to be updated for -06
-//{
-//  frame: {
-//    type: 'ALTSVC',
-//    flags: { },
-//    stream: 0,
+}, {
+  frame: {
+    type: 'ALTSVC',
+    flags: { },
+    stream: 0,
 
-//    maxAge: 31536000,
-//    port: 4443,
-//    protocolID: "h2",
-//    host: "altsvc.example.com",
-//    origin: ""
-//  },
-//  buffer: new Buffer('00001D' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D', 'hex')
-//}, {
-//  frame: {
-//    type: 'ALTSVC',
-//    flags: { },
-//    stream: 0,
-//
-//    maxAge: 31536000,
-//    port: 4443,
-//    protocolID: "h2",
-//    host: "altsvc.example.com",
-//    origin: "https://onlyme.example.com"
-//  },
-//  buffer: new Buffer('000037' + '0A' + '00' + '00000000' + '01E13380' + '115B' + '00' + '02' + '6832' + '12' + '616C747376632E6578616D706C652E636F6D' + '68747470733A2F2F6F6E6C796D652E6578616D706C652E636F6D', 'hex')
-//
-//},
- {
+    maxAge: 31536000,
+    port: 4443,
+    protocolID: "h2",
+    host: "altsvc.example.com",
+    origin: ""
+  },
+  buffer: new Buffer(new Buffer('00002B' + '0A' + '00' + '00000000' + '0000', 'hex') + new Buffer('h2="altsvc.example.com:4443"; ma=31536000', 'ascii'))
+}, {
+  frame: {
+    type: 'ALTSVC',
+    flags: { },
+    stream: 0,
+
+    maxAge: 31536000,
+    port: 4443,
+    protocolID: "h2",
+    host: "altsvc.example.com",
+    origin: "https://onlyme.example.com"
+  },
+  buffer: new Buffer(new Buffer('000045' + '0A' + '00' + '00000000' + '001A', 'hex') + new Buffer('https://onlyme.example.comh2="altsvc.example.com:4443"; ma=31536000', 'ascii'))
+
+}, {
   frame: {
     type: 'BLOCKED',
     flags: { },
     stream: 10
   },
   buffer: new Buffer('000000' + '0B' + '00' + '0000000A', 'hex')
 }];