diff options
Diffstat (limited to 'netwerk/test/unit/test_altsvc.js')
-rw-r--r-- | netwerk/test/unit/test_altsvc.js | 595 |
1 files changed, 595 insertions, 0 deletions
diff --git a/netwerk/test/unit/test_altsvc.js b/netwerk/test/unit/test_altsvc.js new file mode 100644 index 0000000000..b52f9cdffc --- /dev/null +++ b/netwerk/test/unit/test_altsvc.js @@ -0,0 +1,595 @@ +"use strict"; + +const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js"); + +var h2Port; +var prefs; +var http2pref; +var altsvcpref1; +var altsvcpref2; + +// https://foo.example.com:(h2Port) +// https://bar.example.com:(h2Port) <- invalid for bar, but ok for foo +var h1Foo; // server http://foo.example.com:(h1Foo.identity.primaryPort) +var h1Bar; // server http://bar.example.com:(h1bar.identity.primaryPort) + +var otherServer; // server socket listening for other connection. + +var h2FooRoute; // foo.example.com:H2PORT +var h2BarRoute; // bar.example.com:H2PORT +var h2Route; // :H2PORT +var httpFooOrigin; // http://foo.exmaple.com:PORT/ +var httpsFooOrigin; // https://foo.exmaple.com:PORT/ +var httpBarOrigin; // http://bar.example.com:PORT/ +var httpsBarOrigin; // https://bar.example.com:PORT/ + +function run_test() { + h2Port = Services.env.get("MOZHTTP2_PORT"); + Assert.notEqual(h2Port, null); + Assert.notEqual(h2Port, ""); + + // Set to allow the cert presented by our H2 server + do_get_profile(); + prefs = Services.prefs; + + http2pref = prefs.getBoolPref("network.http.http2.enabled"); + altsvcpref1 = prefs.getBoolPref("network.http.altsvc.enabled"); + altsvcpref2 = prefs.getBoolPref("network.http.altsvc.oe", true); + + prefs.setBoolPref("network.http.http2.enabled", true); + prefs.setBoolPref("network.http.altsvc.enabled", true); + prefs.setBoolPref("network.http.altsvc.oe", true); + prefs.setCharPref( + "network.dns.localDomains", + "foo.example.com, bar.example.com" + ); + + // The moz-http2 cert is for foo.example.com and is signed by http2-ca.pem + // so add that cert to the trust list as a signing cert. The same cert is used + // for both h2FooRoute and h2BarRoute though it is only valid for + // the foo.example.com domain name. + let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService( + Ci.nsIX509CertDB + ); + addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u"); + + h1Foo = new HttpServer(); + h1Foo.registerPathHandler("/altsvc-test", h1Server); + h1Foo.registerPathHandler("/.well-known/http-opportunistic", h1ServerWK); + h1Foo.start(-1); + h1Foo.identity.setPrimary( + "http", + "foo.example.com", + h1Foo.identity.primaryPort + ); + + h1Bar = new HttpServer(); + h1Bar.registerPathHandler("/altsvc-test", h1Server); + h1Bar.start(-1); + h1Bar.identity.setPrimary( + "http", + "bar.example.com", + h1Bar.identity.primaryPort + ); + + h2FooRoute = "foo.example.com:" + h2Port; + h2BarRoute = "bar.example.com:" + h2Port; + h2Route = ":" + h2Port; + + httpFooOrigin = "http://foo.example.com:" + h1Foo.identity.primaryPort + "/"; + httpsFooOrigin = "https://" + h2FooRoute + "/"; + httpBarOrigin = "http://bar.example.com:" + h1Bar.identity.primaryPort + "/"; + httpsBarOrigin = "https://" + h2BarRoute + "/"; + dump( + "http foo - " + + httpFooOrigin + + "\n" + + "https foo - " + + httpsFooOrigin + + "\n" + + "http bar - " + + httpBarOrigin + + "\n" + + "https bar - " + + httpsBarOrigin + + "\n" + ); + + doTest1(); +} + +function h1Server(metadata, response) { + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "text/plain", false); + response.setHeader("Connection", "close", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Access-Control-Allow-Method", "GET", false); + response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false); + + try { + var hval = "h2=" + metadata.getHeader("x-altsvc"); + response.setHeader("Alt-Svc", hval, false); + } catch (e) {} + + var body = "Q: What did 0 say to 8? A: Nice Belt!\n"; + response.bodyOutputStream.write(body, body.length); +} + +function h1ServerWK(metadata, response) { + response.setStatusLine(metadata.httpVersion, 200, "OK"); + response.setHeader("Content-Type", "application/json", false); + response.setHeader("Connection", "close", false); + response.setHeader("Cache-Control", "no-cache", false); + response.setHeader("Access-Control-Allow-Origin", "*", false); + response.setHeader("Access-Control-Allow-Method", "GET", false); + response.setHeader("Access-Control-Allow-Headers", "x-altsvc", false); + + var body = '["http://foo.example.com:' + h1Foo.identity.primaryPort + '"]'; + response.bodyOutputStream.write(body, body.length); +} + +function resetPrefs() { + prefs.setBoolPref("network.http.http2.enabled", http2pref); + prefs.setBoolPref("network.http.altsvc.enabled", altsvcpref1); + prefs.setBoolPref("network.http.altsvc.oe", altsvcpref2); + prefs.clearUserPref("network.dns.localDomains"); + prefs.clearUserPref("network.security.ports.banned"); +} + +function makeChan(origin) { + return NetUtil.newChannel({ + uri: origin + "altsvc-test", + loadUsingSystemPrincipal: true, + }).QueryInterface(Ci.nsIHttpChannel); +} + +var origin; +var xaltsvc; +var loadWithoutClearingMappings = false; +var disallowH3 = false; +var disallowH2 = false; +var nextTest; +var expectPass = true; +var waitFor = 0; +var originAttributes = {}; + +var Listener = function () {}; +Listener.prototype = { + onStartRequest: function testOnStartRequest(request) { + Assert.ok(request instanceof Ci.nsIHttpChannel); + + if (expectPass) { + if (!Components.isSuccessCode(request.status)) { + do_throw( + "Channel should have a success code! (" + request.status + ")" + ); + } + Assert.equal(request.responseStatus, 200); + } else { + Assert.equal(Components.isSuccessCode(request.status), false); + } + }, + + onDataAvailable: function testOnDataAvailable(request, stream, off, cnt) { + read_stream(stream, cnt); + }, + + onStopRequest: function testOnStopRequest(request, status) { + var routed = ""; + try { + routed = request.getRequestHeader("Alt-Used"); + } catch (e) {} + dump("routed is " + routed + "\n"); + Assert.equal(Components.isSuccessCode(status), expectPass); + + if (waitFor != 0) { + Assert.equal(routed, ""); + do_test_pending(); + loadWithoutClearingMappings = true; + do_timeout(waitFor, doTest); + waitFor = 0; + xaltsvc = "NA"; + } else if (xaltsvc == "NA") { + Assert.equal(routed, ""); + nextTest(); + } else if (routed == xaltsvc) { + Assert.equal(routed, xaltsvc); // always true, but a useful log + nextTest(); + } else { + dump("poll later for alt svc mapping\n"); + do_test_pending(); + loadWithoutClearingMappings = true; + do_timeout(500, doTest); + } + + do_test_finished(); + }, +}; + +function testsDone() { + dump("testDone\n"); + resetPrefs(); + do_test_pending(); + otherServer.close(); + do_test_pending(); + h1Foo.stop(do_test_finished); + do_test_pending(); + h1Bar.stop(do_test_finished); +} + +function doTest() { + dump("execute doTest " + origin + "\n"); + var chan = makeChan(origin); + var listener = new Listener(); + if (xaltsvc != "NA") { + chan.setRequestHeader("x-altsvc", xaltsvc, false); + } + if (loadWithoutClearingMappings) { + chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; + } else { + chan.loadFlags = + Ci.nsIRequest.LOAD_FRESH_CONNECTION | + Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI; + } + if (disallowH3) { + let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal); + internalChannel.allowHttp3 = false; + disallowH3 = false; + } + if (disallowH2) { + let internalChannel = chan.QueryInterface(Ci.nsIHttpChannelInternal); + internalChannel.allowSpdy = false; + disallowH2 = false; + } + loadWithoutClearingMappings = false; + chan.loadInfo.originAttributes = originAttributes; + chan.asyncOpen(listener); +} + +// xaltsvc is overloaded to do two things.. +// 1] it is sent in the x-altsvc request header, and the response uses the value in the Alt-Svc response header +// 2] the test polls until necko sets Alt-Used to that value (i.e. it uses that route) +// +// When xaltsvc is set to h2Route (i.e. :port with the implied hostname) it doesn't match the alt-used, +// which is always explicit, so it needs to be changed after the channel is created but before the +// listener is invoked + +// http://foo served from h2=:port +function doTest1() { + dump("doTest1()\n"); + origin = httpFooOrigin; + xaltsvc = h2Route; + nextTest = doTest2; + do_test_pending(); + doTest(); + xaltsvc = h2FooRoute; +} + +// http://foo served from h2=foo:port +function doTest2() { + dump("doTest2()\n"); + origin = httpFooOrigin; + xaltsvc = h2FooRoute; + nextTest = doTest3; + do_test_pending(); + doTest(); +} + +// http://foo served from h2=bar:port +// requires cert for foo +function doTest3() { + dump("doTest3()\n"); + origin = httpFooOrigin; + xaltsvc = h2BarRoute; + nextTest = doTest4; + do_test_pending(); + doTest(); +} + +// https://bar should fail because host bar has cert for foo +function doTest4() { + dump("doTest4()\n"); + origin = httpsBarOrigin; + xaltsvc = ""; + expectPass = false; + nextTest = doTest5; + do_test_pending(); + doTest(); +} + +// https://foo no alt-svc (just check cert setup) +function doTest5() { + dump("doTest5()\n"); + origin = httpsFooOrigin; + xaltsvc = "NA"; + expectPass = true; + nextTest = doTest6; + do_test_pending(); + doTest(); +} + +// https://foo via bar (bar has cert for foo) +function doTest6() { + dump("doTest6()\n"); + origin = httpsFooOrigin; + xaltsvc = h2BarRoute; + nextTest = doTest7; + do_test_pending(); + doTest(); +} + +// check again https://bar should fail because host bar has cert for foo +function doTest7() { + dump("doTest7()\n"); + origin = httpsBarOrigin; + xaltsvc = ""; + expectPass = false; + nextTest = doTest8; + do_test_pending(); + doTest(); +} + +// http://bar via h2 on bar +// should not use TLS/h2 because h2BarRoute is not auth'd for bar +// however the test ought to PASS (i.e. get a 200) because fallback +// to plaintext happens.. thus the timeout +function doTest8() { + dump("doTest8()\n"); + origin = httpBarOrigin; + xaltsvc = h2BarRoute; + expectPass = true; + waitFor = 500; + nextTest = doTest9; + do_test_pending(); + doTest(); +} + +// http://bar served from h2=:port, which is like the bar route in 8 +function doTest9() { + dump("doTest9()\n"); + origin = httpBarOrigin; + xaltsvc = h2Route; + expectPass = true; + waitFor = 500; + nextTest = doTest10; + do_test_pending(); + doTest(); + xaltsvc = h2BarRoute; +} + +// check again https://bar should fail because host bar has cert for foo +function doTest10() { + dump("doTest10()\n"); + origin = httpsBarOrigin; + xaltsvc = ""; + expectPass = false; + nextTest = doTest11; + do_test_pending(); + doTest(); +} + +// http://bar served from h2=foo, should fail because host foo only has +// cert for foo. Fail in this case means alt-svc is not used, but content +// is served +function doTest11() { + dump("doTest11()\n"); + origin = httpBarOrigin; + xaltsvc = h2FooRoute; + expectPass = true; + waitFor = 500; + nextTest = doTest12; + do_test_pending(); + doTest(); +} + +// Test 12-15: +// Insert a cache of http://foo served from h2=:port with origin attributes. +function doTest12() { + dump("doTest12()\n"); + origin = httpFooOrigin; + xaltsvc = h2Route; + originAttributes = { + userContextId: 1, + firstPartyDomain: "a.com", + }; + nextTest = doTest13; + do_test_pending(); + doTest(); + xaltsvc = h2FooRoute; +} + +// Make sure we get a cache miss with a different userContextId. +function doTest13() { + dump("doTest13()\n"); + origin = httpFooOrigin; + xaltsvc = "NA"; + originAttributes = { + userContextId: 2, + firstPartyDomain: "a.com", + }; + loadWithoutClearingMappings = true; + nextTest = doTest14; + do_test_pending(); + doTest(); +} + +// Make sure we get a cache miss with a different firstPartyDomain. +function doTest14() { + dump("doTest14()\n"); + origin = httpFooOrigin; + xaltsvc = "NA"; + originAttributes = { + userContextId: 1, + firstPartyDomain: "b.com", + }; + loadWithoutClearingMappings = true; + nextTest = doTest15; + do_test_pending(); + doTest(); +} +// +// Make sure we get a cache hit with the same origin attributes. +function doTest15() { + dump("doTest15()\n"); + origin = httpFooOrigin; + xaltsvc = "NA"; + originAttributes = { + userContextId: 1, + firstPartyDomain: "a.com", + }; + loadWithoutClearingMappings = true; + nextTest = doTest16; + do_test_pending(); + doTest(); + // This ensures a cache hit. + xaltsvc = h2FooRoute; +} + +// Make sure we do not use H2 if it is disabled on a channel. +function doTest16() { + dump("doTest16()\n"); + origin = httpFooOrigin; + xaltsvc = "NA"; + disallowH2 = true; + originAttributes = { + userContextId: 1, + firstPartyDomain: "a.com", + }; + loadWithoutClearingMappings = true; + nextTest = doTest17; + do_test_pending(); + doTest(); +} + +// Make sure we use H2 if only Http3 is disabled on a channel. +function doTest17() { + dump("doTest17()\n"); + origin = httpFooOrigin; + xaltsvc = h2Route; + disallowH3 = true; + originAttributes = { + userContextId: 1, + firstPartyDomain: "a.com", + }; + loadWithoutClearingMappings = true; + nextTest = doTest18; + do_test_pending(); + doTest(); + // This should ensures a cache hit. + xaltsvc = h2FooRoute; +} + +// Check we don't connect to blocked ports +function doTest18() { + dump("doTest18()\n"); + origin = httpFooOrigin; + nextTest = testsDone; + otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance( + Ci.nsIServerSocket + ); + otherServer.init(-1, true, -1); + xaltsvc = "localhost:" + otherServer.port; + Services.prefs.setCharPref( + "network.security.ports.banned", + "" + otherServer.port + ); + dump("Blocked port: " + otherServer.port); + waitFor = 500; + otherServer.asyncListen({ + onSocketAccepted() { + Assert.ok(false, "Got connection to socket when we didn't expect it!"); + }, + onStopListening() { + // We get closed when the entire file is done, which guarantees we get the socket accept + // if we do connect to the alt-svc header + do_test_finished(); + }, + }); + nextTest = doTest19; + do_test_pending(); + doTest(); +} + +// Check we don't connect to blocked ports +function doTest19() { + dump("doTest19()\n"); + origin = httpFooOrigin; + nextTest = testsDone; + otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance( + Ci.nsIServerSocket + ); + const BAD_PORT_U32 = 6667 + 65536; + otherServer.init(BAD_PORT_U32, true, -1); + Assert.ok(otherServer.port == 6667, "Trying to listen on port 6667"); + xaltsvc = "localhost:" + BAD_PORT_U32; + dump("Blocked port: " + otherServer.port); + waitFor = 500; + otherServer.asyncListen({ + onSocketAccepted() { + Assert.ok(false, "Got connection to socket when we didn't expect it!"); + }, + onStopListening() { + // We get closed when the entire file is done, which guarantees we get the socket accept + // if we do connect to the alt-svc header + do_test_finished(); + }, + }); + nextTest = doTest20; + do_test_pending(); + doTest(); +} +function doTest20() { + dump("doTest20()\n"); + origin = httpFooOrigin; + nextTest = testsDone; + otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance( + Ci.nsIServerSocket + ); + const BAD_PORT_U64 = 6666 + 429496729; + otherServer.init(6666, true, -1); + Assert.ok(otherServer.port == 6666, "Trying to listen on port 6666"); + xaltsvc = "localhost:" + BAD_PORT_U64; + dump("Blocked port: " + otherServer.port); + waitFor = 500; + otherServer.asyncListen({ + onSocketAccepted() { + Assert.ok(false, "Got connection to socket when we didn't expect it!"); + }, + onStopListening() { + // We get closed when the entire file is done, which guarantees we get the socket accept + // if we do connect to the alt-svc header + do_test_finished(); + }, + }); + nextTest = doTest21; + do_test_pending(); + doTest(); +} +// Port 65535 should be OK +function doTest21() { + dump("doTest21()\n"); + origin = httpFooOrigin; + nextTest = testsDone; + otherServer = Cc["@mozilla.org/network/server-socket;1"].createInstance( + Ci.nsIServerSocket + ); + const GOOD_PORT = 65535; + otherServer.init(65535, true, -1); + Assert.ok(otherServer.port == 65535, "Trying to listen on port 65535"); + xaltsvc = "localhost:" + GOOD_PORT; + dump("Allowed port: " + otherServer.port); + waitFor = 500; + otherServer.asyncListen({ + onSocketAccepted() { + Assert.ok(true, "Got connection to socket when we didn't expect it!"); + }, + onStopListening() { + // We get closed when the entire file is done, which guarantees we get the socket accept + // if we do connect to the alt-svc header + do_test_finished(); + }, + }); + do_test_pending(); + doTest(); +} |