michael@0: // test spdy/3 michael@0: michael@0: var Ci = Components.interfaces; michael@0: var Cc = Components.classes; michael@0: michael@0: // Generate a small and a large post with known pre-calculated md5 sums michael@0: function generateContent(size) { michael@0: var content = ""; michael@0: for (var i = 0; i < size; i++) { michael@0: content += "0"; michael@0: } michael@0: return content; michael@0: } michael@0: michael@0: var posts = []; michael@0: posts.push(generateContent(10)); michael@0: posts.push(generateContent(250000)); michael@0: michael@0: // pre-calculated md5sums (in hex) of the above posts michael@0: var md5s = ['f1b708bba17f1ce948dc979f4d7092bc', michael@0: '2ef8d3b6c8f329318eb1a119b12622b6']; michael@0: michael@0: var bigListenerData = generateContent(128 * 1024); michael@0: var bigListenerMD5 = '8f607cfdd2c87d6a7eedb657dafbd836'; michael@0: michael@0: function checkIsSpdy(request) { michael@0: try { michael@0: if (request.getResponseHeader("X-Firefox-Spdy") == "3") { michael@0: if (request.getResponseHeader("X-Connection-Spdy") == "yes") { michael@0: return true; michael@0: } michael@0: return false; // Weird case, but the server disagrees with us michael@0: } michael@0: } catch (e) { michael@0: // Nothing to do here michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: var SpdyCheckListener = function() {}; michael@0: michael@0: SpdyCheckListener.prototype = { michael@0: onStartRequestFired: false, michael@0: onDataAvailableFired: false, michael@0: isSpdyConnection: false, michael@0: michael@0: onStartRequest: function testOnStartRequest(request, ctx) { michael@0: this.onStartRequestFired = true; michael@0: michael@0: if (!Components.isSuccessCode(request.status)) michael@0: do_throw("Channel should have a success code! (" + request.status + ")"); michael@0: if (!(request instanceof Components.interfaces.nsIHttpChannel)) michael@0: do_throw("Expecting an HTTP channel"); michael@0: michael@0: do_check_eq(request.responseStatus, 200); michael@0: do_check_eq(request.requestSucceeded, true); michael@0: }, michael@0: michael@0: onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) { michael@0: this.onDataAvailableFired = true; michael@0: this.isSpdyConnection = checkIsSpdy(request); michael@0: michael@0: read_stream(stream, cnt); michael@0: }, michael@0: michael@0: onStopRequest: function testOnStopRequest(request, ctx, status) { michael@0: do_check_true(this.onStartRequestFired); michael@0: do_check_true(this.onDataAvailableFired); michael@0: do_check_true(this.isSpdyConnection); michael@0: michael@0: run_next_test(); michael@0: do_test_finished(); michael@0: } michael@0: }; michael@0: michael@0: /* michael@0: * Support for testing valid multiplexing of streams michael@0: */ michael@0: michael@0: var multiplexContent = generateContent(30*1024); michael@0: var completed_channels = []; michael@0: function register_completed_channel(listener) { michael@0: completed_channels.push(listener); michael@0: if (completed_channels.length == 2) { michael@0: do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID); michael@0: run_next_test(); michael@0: do_test_finished(); michael@0: } michael@0: } michael@0: michael@0: /* Listener class to control the testing of multiplexing */ michael@0: var SpdyMultiplexListener = function() {}; michael@0: michael@0: SpdyMultiplexListener.prototype = new SpdyCheckListener(); michael@0: michael@0: SpdyMultiplexListener.prototype.streamID = 0; michael@0: SpdyMultiplexListener.prototype.buffer = ""; michael@0: michael@0: SpdyMultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { michael@0: this.onDataAvailableFired = true; michael@0: this.isSpdyConnection = checkIsSpdy(request); michael@0: this.streamID = parseInt(request.getResponseHeader("X-Spdy-StreamID")); michael@0: var data = read_stream(stream, cnt); michael@0: this.buffer = this.buffer.concat(data); michael@0: }; michael@0: michael@0: SpdyMultiplexListener.prototype.onStopRequest = function(request, ctx, status) { michael@0: do_check_true(this.onStartRequestFired); michael@0: do_check_true(this.onDataAvailableFired); michael@0: do_check_true(this.isSpdyConnection); michael@0: do_check_true(this.buffer == multiplexContent); michael@0: michael@0: // This is what does most of the hard work for us michael@0: register_completed_channel(this); michael@0: }; michael@0: michael@0: // Does the appropriate checks for header gatewaying michael@0: var SpdyHeaderListener = function(value) { michael@0: this.value = value michael@0: }; michael@0: michael@0: SpdyHeaderListener.prototype = new SpdyCheckListener(); michael@0: SpdyHeaderListener.prototype.value = ""; michael@0: michael@0: SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { michael@0: this.onDataAvailableFired = true; michael@0: this.isSpdyConnection = checkIsSpdy(request); michael@0: do_check_eq(request.getResponseHeader("X-Received-Test-Header"), this.value); michael@0: read_stream(stream, cnt); michael@0: }; michael@0: michael@0: var SpdyPushListener = function() {}; michael@0: michael@0: SpdyPushListener.prototype = new SpdyCheckListener(); michael@0: michael@0: SpdyPushListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { michael@0: this.onDataAvailableFired = true; michael@0: this.isSpdyConnection = checkIsSpdy(request); michael@0: if (ctx.originalURI.spec == "https://localhost:4443/push.js" || michael@0: ctx.originalURI.spec == "https://localhost:4443/push2.js") { michael@0: do_check_eq(request.getResponseHeader("pushed"), "yes"); michael@0: } michael@0: read_stream(stream, cnt); michael@0: }; michael@0: michael@0: // Does the appropriate checks for a large GET response michael@0: var SpdyBigListener = function() {}; michael@0: michael@0: SpdyBigListener.prototype = new SpdyCheckListener(); michael@0: SpdyBigListener.prototype.buffer = ""; michael@0: michael@0: SpdyBigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { michael@0: this.onDataAvailableFired = true; michael@0: this.isSpdyConnection = checkIsSpdy(request); michael@0: this.buffer = this.buffer.concat(read_stream(stream, cnt)); michael@0: // We know the server should send us the same data as our big post will be, michael@0: // so the md5 should be the same michael@0: do_check_eq(bigListenerMD5, request.getResponseHeader("X-Expected-MD5")); michael@0: }; michael@0: michael@0: SpdyBigListener.prototype.onStopRequest = function(request, ctx, status) { michael@0: do_check_true(this.onStartRequestFired); michael@0: do_check_true(this.onDataAvailableFired); michael@0: do_check_true(this.isSpdyConnection); michael@0: michael@0: // Don't want to flood output, so don't use do_check_eq michael@0: do_check_true(this.buffer == bigListenerData); michael@0: michael@0: run_next_test(); michael@0: do_test_finished(); michael@0: }; michael@0: michael@0: // Does the appropriate checks for POSTs michael@0: var SpdyPostListener = function(expected_md5) { michael@0: this.expected_md5 = expected_md5; michael@0: }; michael@0: michael@0: SpdyPostListener.prototype = new SpdyCheckListener(); michael@0: SpdyPostListener.prototype.expected_md5 = ""; michael@0: michael@0: SpdyPostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) { michael@0: this.onDataAvailableFired = true; michael@0: this.isSpdyConnection = checkIsSpdy(request); michael@0: read_stream(stream, cnt); michael@0: do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5")); michael@0: }; michael@0: michael@0: function makeChan(url) { michael@0: var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService); michael@0: var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel); michael@0: michael@0: return chan; michael@0: } michael@0: michael@0: // Make sure we make a spdy connection and both us and the server mark it as such michael@0: function test_spdy_basic() { michael@0: var chan = makeChan("https://localhost:4443/"); michael@0: var listener = new SpdyCheckListener(); michael@0: chan.asyncOpen(listener, null); michael@0: } michael@0: michael@0: // Support for making sure XHR works over SPDY michael@0: function checkXhr(xhr) { michael@0: if (xhr.readyState != 4) { michael@0: return; michael@0: } michael@0: michael@0: do_check_eq(xhr.status, 200); michael@0: do_check_eq(checkIsSpdy(xhr), true); michael@0: run_next_test(); michael@0: do_test_finished(); michael@0: } michael@0: michael@0: // Fires off an XHR request over SPDY michael@0: function test_spdy_xhr() { michael@0: var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] michael@0: .createInstance(Ci.nsIXMLHttpRequest); michael@0: req.open("GET", "https://localhost:4443/", true); michael@0: req.addEventListener("readystatechange", function (evt) { checkXhr(req); }, michael@0: false); michael@0: req.send(null); michael@0: } michael@0: michael@0: // Test to make sure we get multiplexing right michael@0: function test_spdy_multiplex() { michael@0: var chan1 = makeChan("https://localhost:4443/multiplex1"); michael@0: var chan2 = makeChan("https://localhost:4443/multiplex2"); michael@0: var listener1 = new SpdyMultiplexListener(); michael@0: var listener2 = new SpdyMultiplexListener(); michael@0: chan1.asyncOpen(listener1, null); michael@0: chan2.asyncOpen(listener2, null); michael@0: } michael@0: michael@0: // Test to make sure we gateway non-standard headers properly michael@0: function test_spdy_header() { michael@0: var chan = makeChan("https://localhost:4443/header"); michael@0: var hvalue = "Headers are fun"; michael@0: var listener = new SpdyHeaderListener(hvalue); michael@0: chan.setRequestHeader("X-Test-Header", hvalue, false); michael@0: chan.asyncOpen(listener, null); michael@0: } michael@0: michael@0: function test_spdy_push1() { michael@0: var chan = makeChan("https://localhost:4443/push"); michael@0: chan.loadGroup = loadGroup; michael@0: var listener = new SpdyPushListener(); michael@0: chan.asyncOpen(listener, chan); michael@0: } michael@0: michael@0: function test_spdy_push2() { michael@0: var chan = makeChan("https://localhost:4443/push.js"); michael@0: chan.loadGroup = loadGroup; michael@0: var listener = new SpdyPushListener(); michael@0: chan.asyncOpen(listener, chan); michael@0: } michael@0: michael@0: function test_spdy_push3() { michael@0: var chan = makeChan("https://localhost:4443/push2"); michael@0: chan.loadGroup = loadGroup; michael@0: var listener = new SpdyPushListener(); michael@0: chan.asyncOpen(listener, chan); michael@0: } michael@0: michael@0: function test_spdy_push4() { michael@0: var chan = makeChan("https://localhost:4443/push2.js"); michael@0: chan.loadGroup = loadGroup; michael@0: var listener = new SpdyPushListener(); michael@0: chan.asyncOpen(listener, chan); michael@0: } michael@0: michael@0: // Make sure we handle GETs that cover more than 2 frames properly michael@0: function test_spdy_big() { michael@0: var chan = makeChan("https://localhost:4443/big"); michael@0: var listener = new SpdyBigListener(); michael@0: chan.asyncOpen(listener, null); michael@0: } michael@0: michael@0: // Support for doing a POST michael@0: function do_post(content, chan, listener) { michael@0: var stream = Cc["@mozilla.org/io/string-input-stream;1"] michael@0: .createInstance(Ci.nsIStringInputStream); michael@0: stream.data = content; michael@0: michael@0: var uchan = chan.QueryInterface(Ci.nsIUploadChannel); michael@0: uchan.setUploadStream(stream, "text/plain", stream.available()); michael@0: michael@0: chan.requestMethod = "POST"; michael@0: michael@0: chan.asyncOpen(listener, null); michael@0: } michael@0: michael@0: // Make sure we can do a simple POST michael@0: function test_spdy_post() { michael@0: var chan = makeChan("https://localhost:4443/post"); michael@0: var listener = new SpdyPostListener(md5s[0]); michael@0: do_post(posts[0], chan, listener); michael@0: } michael@0: michael@0: // Make sure we can do a POST that covers more than 2 frames michael@0: function test_spdy_post_big() { michael@0: var chan = makeChan("https://localhost:4443/post"); michael@0: var listener = new SpdyPostListener(md5s[1]); michael@0: do_post(posts[1], chan, listener); michael@0: } michael@0: michael@0: // hack - the header test resets the multiplex object on the server, michael@0: // so make sure header is always run before the multiplex test. michael@0: // michael@0: // make sure post_big runs first to test race condition in restarting michael@0: // a stalled stream when a SETTINGS frame arrives michael@0: var tests = [ test_spdy_post_big michael@0: , test_spdy_basic michael@0: , test_spdy_push1 michael@0: , test_spdy_push2 michael@0: , test_spdy_push3 michael@0: , test_spdy_push4 michael@0: , test_spdy_xhr michael@0: , test_spdy_header michael@0: , test_spdy_multiplex michael@0: , test_spdy_big michael@0: , test_spdy_post michael@0: ]; michael@0: var current_test = 0; michael@0: michael@0: function run_next_test() { michael@0: if (current_test < tests.length) { michael@0: tests[current_test](); michael@0: current_test++; michael@0: do_test_pending(); michael@0: } michael@0: } michael@0: michael@0: // Support for making sure we can talk to the invalid cert the server presents michael@0: var CertOverrideListener = function(host, port, bits) { michael@0: this.host = host; michael@0: if (port) { michael@0: this.port = port; michael@0: } michael@0: this.bits = bits; michael@0: }; michael@0: michael@0: CertOverrideListener.prototype = { michael@0: host: null, michael@0: port: -1, michael@0: bits: null, michael@0: michael@0: getInterface: function(aIID) { michael@0: return this.QueryInterface(aIID); michael@0: }, michael@0: michael@0: QueryInterface: function(aIID) { michael@0: if (aIID.equals(Ci.nsIBadCertListener2) || michael@0: aIID.equals(Ci.nsIInterfaceRequestor) || michael@0: aIID.equals(Ci.nsISupports)) michael@0: return this; michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: michael@0: notifyCertProblem: function(socketInfo, sslStatus, targetHost) { michael@0: var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert; michael@0: var cos = Cc["@mozilla.org/security/certoverride;1"]. michael@0: getService(Ci.nsICertOverrideService); michael@0: cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false); michael@0: return true; michael@0: }, michael@0: }; michael@0: michael@0: function addCertOverride(host, port, bits) { michael@0: var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] michael@0: .createInstance(Ci.nsIXMLHttpRequest); michael@0: try { michael@0: var url; michael@0: if (port) { michael@0: url = "https://" + host + ":" + port + "/"; michael@0: } else { michael@0: url = "https://" + host + "/"; michael@0: } michael@0: req.open("GET", url, false); michael@0: req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits); michael@0: req.send(null); michael@0: } catch (e) { michael@0: // This will fail since the server is not trusted yet michael@0: } michael@0: } michael@0: michael@0: var prefs; michael@0: var spdypref; michael@0: var spdy3pref; michael@0: var spdypush; michael@0: michael@0: var loadGroup; michael@0: michael@0: function resetPrefs() { michael@0: prefs.setBoolPref("network.http.spdy.enabled", spdypref); michael@0: prefs.setBoolPref("network.http.spdy.enabled.v3", spdy3pref); michael@0: prefs.setBoolPref("network.http.spdy.allow-push", spdypush); michael@0: } michael@0: michael@0: function run_test() { michael@0: // Set to allow the cert presented by our SPDY server michael@0: do_get_profile(); michael@0: var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); michael@0: var oldPref = prefs.getIntPref("network.http.speculative-parallel-limit"); michael@0: prefs.setIntPref("network.http.speculative-parallel-limit", 0); michael@0: michael@0: addCertOverride("localhost", 4443, michael@0: Ci.nsICertOverrideService.ERROR_UNTRUSTED | michael@0: Ci.nsICertOverrideService.ERROR_MISMATCH | michael@0: Ci.nsICertOverrideService.ERROR_TIME); michael@0: michael@0: prefs.setIntPref("network.http.speculative-parallel-limit", oldPref); michael@0: michael@0: // Enable all versions of spdy to see that we auto negotiate spdy/3 michael@0: spdypref = prefs.getBoolPref("network.http.spdy.enabled"); michael@0: spdy3pref = prefs.getBoolPref("network.http.spdy.enabled.v3"); michael@0: spdypush = prefs.getBoolPref("network.http.spdy.allow-push"); michael@0: prefs.setBoolPref("network.http.spdy.enabled", true); michael@0: prefs.setBoolPref("network.http.spdy.enabled.v3", true); michael@0: prefs.setBoolPref("network.http.spdy.allow-push", true); michael@0: michael@0: loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(Ci.nsILoadGroup); michael@0: michael@0: // And make go! michael@0: run_next_test(); michael@0: }