"use strict";
var h2Port;
var prefs;
var http2pref;
var altsvcpref1;
// https://foo.example.com:(h2Port)
// https://bar.example.com:(h2Port) <- invalid for bar, but ok for foo
var otherServer;
// server socket listening for other connection.
var h2FooRoute;
// foo.example.com:H2PORT
var h2BarRoute;
// bar.example.com:H2PORT
var httpsFooOrigin;
// https://foo.exmaple.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");
prefs.setBoolPref(
"network.http.http2.enabled",
true);
prefs.setBoolPref(
"network.http.altsvc.enabled",
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");
h2FooRoute =
"foo.example.com:" + h2Port;
h2BarRoute =
"bar.example.com:" + h2Port;
httpsFooOrigin =
"https://" + h2FooRoute + "/";
httpsBarOrigin =
"https://" + h2BarRoute + "/";
dump(
"https foo - " +
httpsFooOrigin +
"\n" +
"https bar - " +
httpsBarOrigin +
"\n"
);
doTest1();
}
function resetPrefs() {
prefs.setBoolPref(
"network.http.http2.enabled", http2pref);
prefs.setBoolPref(
"network.http.altsvc.enabled", altsvcpref1);
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 nextTest;
var expectPass =
true;
var waitFor =
0;
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) {}
// When a direct connection wins the Happy Eyeballs race, the alt-svc route
// is dropped by setting "Alt-Used: 0" (see RemoveAlternateServiceUsedHeader),
// which means the same thing as an absent header here.
if (routed ==
"0") {
routed =
"";
}
dump(
"routed is " + routed +
"\n");
Assert.equal(Components.isSuccessCode(status), expectPass);
if (waitFor !=
0) {
Assert.equal(routed,
"");
do_test_pending();
loadWithoutClearingMappings =
true;
// With Happy Eyeballs the alt-svc connection shares the origin's
// ConnectionEntry, so the retry would otherwise reuse the origin
// connection and be wrongly tagged as alt-svc-routed. Drop connections so
// the retry re-resolves the alt-svc from scratch.
Services.obs.notifyObservers(
null,
"net:cancel-all-connections");
Services.dns.clearCache(
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();
}
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;
}
loadWithoutClearingMappings =
false;
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
// https://bar should fail because host bar has cert for foo
function doTest1() {
dump(
"doTest1()\n");
origin = httpsBarOrigin;
xaltsvc =
"";
expectPass =
false;
nextTest = doTest2;
do_test_pending();
doTest();
}
// https://foo no alt-svc (just check cert setup)
function doTest2() {
dump(
"doTest2()\n");
origin = httpsFooOrigin;
xaltsvc =
"NA";
expectPass =
true;
nextTest = doTest3;
do_test_pending();
doTest();
}
// https://foo via bar (bar has cert for foo)
function doTest3() {
dump(
"doTest3()\n");
origin = httpsFooOrigin;
xaltsvc = h2BarRoute;
nextTest = doTest4;
do_test_pending();
doTest();
}
// check again 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();
}
// check again https://bar should fail because host bar has cert for foo
function doTest5() {
dump(
"doTest5()\n");
origin = httpsBarOrigin;
xaltsvc =
"";
expectPass =
false;
nextTest = doTest6;
do_test_pending();
doTest();
}
// Check we don't connect to blocked ports
function doTest6() {
dump(
"doTest6()\n");
origin = httpsFooOrigin;
expectPass =
true;
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() {
do_test_finished();
},
});
nextTest = doTest7;
do_test_pending();
doTest();
}
// Check we don't connect to blocked ports
function doTest7() {
dump(
"doTest7()\n");
origin = httpsFooOrigin;
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.equal(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() {
do_test_finished();
},
});
nextTest = doTest8;
do_test_pending();
doTest();
}
function doTest8() {
dump(
"doTest8()\n");
origin = httpsFooOrigin;
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.equal(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() {
do_test_finished();
},
});
nextTest = doTest9;
do_test_pending();
doTest();
}
// Port 65535 should be OK
function doTest9() {
dump(
"doTest9()\n");
origin = httpsFooOrigin;
nextTest = testsDone;
otherServer = Cc[
"@mozilla.org/network/server-socket;1"].createInstance(
Ci.nsIServerSocket
);
const GOOD_PORT =
65535;
otherServer.init(
65535,
true, -
1);
Assert.equal(otherServer.port,
65535,
"Trying to listen on port 65535");
xaltsvc =
"localhost:" + GOOD_PORT;
dump(
"Allowed port: " + otherServer.port);
waitFor =
500;
otherServer.asyncListen({
onSocketAccepted(socket, transport) {
Assert.ok(
true,
"Got connection to socket");
let out = transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,
0,
0);
out.write(
"not-tls\n",
8);
out.close();
},
onStopListening() {
do_test_finished();
},
});
do_test_pending();
doTest();
}