432 lines
13 KiB
JavaScript
432 lines
13 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */
|
|
/* vim: set ts=4 sts=4 et sw=4 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
var CC = Components.Constructor;
|
|
const ServerSocket = CC(
|
|
"@mozilla.org/network/server-socket;1",
|
|
"nsIServerSocket",
|
|
"init"
|
|
);
|
|
var serv;
|
|
var ios;
|
|
|
|
/** Example local IP addresses (literal IP address hostname).
|
|
*
|
|
* Note: for IPv6 Unique Local and Link Local, a wider range of addresses is
|
|
* set aside than those most commonly used. Technically, link local addresses
|
|
* include those beginning with fe80:: through febf::, although in practise
|
|
* only fe80:: is used. Necko code blocks speculative connections for the wider
|
|
* range; hence, this test considers that range too.
|
|
*/
|
|
var localIPv4Literals = [
|
|
// IPv4 RFC1918 \
|
|
"10.0.0.1",
|
|
"10.10.10.10",
|
|
"10.255.255.255", // 10/8
|
|
"172.16.0.1",
|
|
"172.23.172.12",
|
|
"172.31.255.255", // 172.16/20
|
|
"192.168.0.1",
|
|
"192.168.192.168",
|
|
"192.168.255.255", // 192.168/16
|
|
// IPv4 Link Local
|
|
"169.254.0.1",
|
|
"169.254.192.154",
|
|
"169.254.255.255", // 169.254/16
|
|
];
|
|
var localIPv6Literals = [
|
|
// IPv6 Unique Local fc00::/7
|
|
"fc00::1",
|
|
"fdfe:dcba:9876:abcd:ef01:2345:6789:abcd",
|
|
"fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
|
// IPv6 Link Local fe80::/10
|
|
"fe80::1",
|
|
"fe80::abcd:ef01:2345:6789",
|
|
"febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
|
|
];
|
|
var localIPLiterals = localIPv4Literals.concat(localIPv6Literals);
|
|
|
|
/** Test function list and descriptions.
|
|
*/
|
|
var testList = [
|
|
test_localhost_http_speculative_connect,
|
|
test_localhost_https_speculative_connect,
|
|
test_hostnames_resolving_to_local_addresses,
|
|
test_proxies_with_local_addresses,
|
|
test_speculative_connect_with_proxy_filter,
|
|
];
|
|
|
|
var testDescription = [
|
|
"Expect pass with localhost, http",
|
|
"Expect pass with localhost, https",
|
|
"Expect failure with resolved local IPs",
|
|
"Expect failure for proxies with local IPs",
|
|
"Expect failure without notification callbacks",
|
|
];
|
|
|
|
var testIdx = 0;
|
|
var hostIdx = 0;
|
|
|
|
/** TestServer
|
|
*
|
|
* Implements nsIServerSocket for test_speculative_connect.
|
|
*/
|
|
function TestServer() {
|
|
this.listener = ServerSocket(-1, true, -1);
|
|
this.listener.asyncListen(this);
|
|
}
|
|
|
|
TestServer.prototype = {
|
|
QueryInterface: ChromeUtils.generateQI(["nsIServerSocket"]),
|
|
onSocketAccepted() {
|
|
try {
|
|
this.listener.close();
|
|
} catch (e) {}
|
|
Assert.ok(true);
|
|
next_test();
|
|
},
|
|
|
|
onStopListening() {},
|
|
};
|
|
|
|
/** TestFailedStreamCallback
|
|
*
|
|
* Implements nsI[Input|Output]StreamCallback for socket layer tests.
|
|
* Expect failure in all cases
|
|
*/
|
|
function TestFailedStreamCallback(transport, hostname, next) {
|
|
this.transport = transport;
|
|
this.hostname = hostname;
|
|
this.next = next;
|
|
this.dummyContent = "G";
|
|
this.closed = false;
|
|
}
|
|
|
|
TestFailedStreamCallback.prototype = {
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
"nsIInputStreamCallback",
|
|
"nsIOutputStreamCallback",
|
|
]),
|
|
processException(e) {
|
|
if (this.closed) {
|
|
return;
|
|
}
|
|
do_check_instanceof(e, Ci.nsIException);
|
|
// A refusal to connect speculatively should throw an error.
|
|
Assert.equal(e.result, Cr.NS_ERROR_CONNECTION_REFUSED);
|
|
this.closed = true;
|
|
this.transport.close(Cr.NS_BINDING_ABORTED);
|
|
this.next();
|
|
},
|
|
onOutputStreamReady(outstream) {
|
|
info("outputstream handler.");
|
|
Assert.notEqual(typeof outstream, undefined);
|
|
try {
|
|
outstream.write(this.dummyContent, this.dummyContent.length);
|
|
} catch (e) {
|
|
this.processException(e);
|
|
return;
|
|
}
|
|
info("no exception on write. Wait for read.");
|
|
},
|
|
onInputStreamReady(instream) {
|
|
info("inputstream handler.");
|
|
Assert.notEqual(typeof instream, undefined);
|
|
try {
|
|
instream.available();
|
|
} catch (e) {
|
|
this.processException(e);
|
|
return;
|
|
}
|
|
do_throw("Speculative Connect should have failed for " + this.hostname);
|
|
this.transport.close(Cr.NS_BINDING_ABORTED);
|
|
this.next();
|
|
},
|
|
};
|
|
|
|
/** test_localhost_http_speculative_connect
|
|
*
|
|
* Tests a basic positive case using nsIOService.SpeculativeConnect:
|
|
* connecting to localhost via http.
|
|
*/
|
|
function test_localhost_http_speculative_connect() {
|
|
serv = new TestServer();
|
|
var ssm = Services.scriptSecurityManager;
|
|
var URI = ios.newURI(
|
|
"http://localhost:" + serv.listener.port + "/just/a/test"
|
|
);
|
|
var principal = ssm.createContentPrincipal(URI, {});
|
|
|
|
ios
|
|
.QueryInterface(Ci.nsISpeculativeConnect)
|
|
.speculativeConnect(URI, principal, null, false);
|
|
}
|
|
|
|
/** test_localhost_https_speculative_connect
|
|
*
|
|
* Tests a basic positive case using nsIOService.SpeculativeConnect:
|
|
* connecting to localhost via https.
|
|
*/
|
|
function test_localhost_https_speculative_connect() {
|
|
serv = new TestServer();
|
|
var ssm = Services.scriptSecurityManager;
|
|
var URI = ios.newURI(
|
|
"https://localhost:" + serv.listener.port + "/just/a/test"
|
|
);
|
|
var principal = ssm.createContentPrincipal(URI, {});
|
|
|
|
ios
|
|
.QueryInterface(Ci.nsISpeculativeConnect)
|
|
.speculativeConnect(URI, principal, null, false);
|
|
}
|
|
|
|
/* Speculative connections should not be allowed for hosts with local IP
|
|
* addresses (Bug 853423). That list includes:
|
|
* -- IPv4 RFC1918 and Link Local Addresses.
|
|
* -- IPv6 Unique and Link Local Addresses.
|
|
*
|
|
* Two tests are required:
|
|
* 1. Verify IP Literals passed to the SpeculativeConnect API.
|
|
* 2. Verify hostnames that need to be resolved at the socket layer.
|
|
*/
|
|
|
|
/** test_hostnames_resolving_to_addresses
|
|
*
|
|
* Common test function for resolved hostnames. Takes a list of hosts, a
|
|
* boolean to determine if the test is expected to succeed or fail, and a
|
|
* function to call the next test case.
|
|
*/
|
|
function test_hostnames_resolving_to_addresses(host, next) {
|
|
info(host);
|
|
var sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
|
|
Ci.nsISocketTransportService
|
|
);
|
|
Assert.notEqual(typeof sts, undefined);
|
|
var transport = sts.createTransport([], host, 80, null, null);
|
|
Assert.notEqual(typeof transport, undefined);
|
|
|
|
transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
|
|
transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
|
|
transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1);
|
|
Assert.equal(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT));
|
|
|
|
var outStream = transport.openOutputStream(
|
|
Ci.nsITransport.OPEN_UNBUFFERED,
|
|
0,
|
|
0
|
|
);
|
|
var inStream = transport.openInputStream(0, 0, 0);
|
|
Assert.notEqual(typeof outStream, undefined);
|
|
Assert.notEqual(typeof inStream, undefined);
|
|
|
|
var callback = new TestFailedStreamCallback(transport, host, next);
|
|
Assert.notEqual(typeof callback, undefined);
|
|
|
|
// Need to get main thread pointer to ensure nsSocketTransport::AsyncWait
|
|
// adds callback to ns*StreamReadyEvent on main thread, and doesn't
|
|
// addref off the main thread.
|
|
var gThreadManager = Services.tm;
|
|
var mainThread = gThreadManager.currentThread;
|
|
|
|
try {
|
|
outStream
|
|
.QueryInterface(Ci.nsIAsyncOutputStream)
|
|
.asyncWait(callback, 0, 0, mainThread);
|
|
inStream
|
|
.QueryInterface(Ci.nsIAsyncInputStream)
|
|
.asyncWait(callback, 0, 0, mainThread);
|
|
} catch (e) {
|
|
do_throw("asyncWait should not fail!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* test_hostnames_resolving_to_local_addresses
|
|
*
|
|
* Creates an nsISocketTransport and simulates a speculative connect request
|
|
* for a hostname that resolves to a local IP address.
|
|
* Runs asynchronously; on test success (i.e. failure to connect), the callback
|
|
* will call this function again until all hostnames in the test list are done.
|
|
*
|
|
* Note: This test also uses an IP literal for the hostname. This should be ok,
|
|
* as the socket layer will ask for the hostname to be resolved anyway, and DNS
|
|
* code should return a numerical version of the address internally.
|
|
*/
|
|
function test_hostnames_resolving_to_local_addresses() {
|
|
if (hostIdx >= localIPLiterals.length) {
|
|
// No more local IP addresses; move on.
|
|
next_test();
|
|
return;
|
|
}
|
|
var host = localIPLiterals[hostIdx++];
|
|
// Test another local IP address when the current one is done.
|
|
var next = test_hostnames_resolving_to_local_addresses;
|
|
test_hostnames_resolving_to_addresses(host, next);
|
|
}
|
|
|
|
/** test_speculative_connect_with_host_list
|
|
*
|
|
* Common test function for resolved proxy hosts. Takes a list of hosts, a
|
|
* boolean to determine if the test is expected to succeed or fail, and a
|
|
* function to call the next test case.
|
|
*/
|
|
function test_proxies(proxyHost, next) {
|
|
info("Proxy: " + proxyHost);
|
|
var sts = Cc["@mozilla.org/network/socket-transport-service;1"].getService(
|
|
Ci.nsISocketTransportService
|
|
);
|
|
Assert.notEqual(typeof sts, undefined);
|
|
var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
|
|
Assert.notEqual(typeof pps, undefined);
|
|
|
|
var proxyInfo = pps.newProxyInfo("http", proxyHost, 8080, "", "", 0, 1, null);
|
|
Assert.notEqual(typeof proxyInfo, undefined);
|
|
|
|
var transport = sts.createTransport([], "dummyHost", 80, proxyInfo, null);
|
|
Assert.notEqual(typeof transport, undefined);
|
|
|
|
transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918;
|
|
|
|
transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1);
|
|
Assert.equal(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT));
|
|
transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1);
|
|
|
|
var outStream = transport.openOutputStream(
|
|
Ci.nsITransport.OPEN_UNBUFFERED,
|
|
0,
|
|
0
|
|
);
|
|
var inStream = transport.openInputStream(0, 0, 0);
|
|
Assert.notEqual(typeof outStream, undefined);
|
|
Assert.notEqual(typeof inStream, undefined);
|
|
|
|
var callback = new TestFailedStreamCallback(transport, proxyHost, next);
|
|
Assert.notEqual(typeof callback, undefined);
|
|
|
|
// Need to get main thread pointer to ensure nsSocketTransport::AsyncWait
|
|
// adds callback to ns*StreamReadyEvent on main thread, and doesn't
|
|
// addref off the main thread.
|
|
var gThreadManager = Services.tm;
|
|
var mainThread = gThreadManager.currentThread;
|
|
|
|
try {
|
|
outStream
|
|
.QueryInterface(Ci.nsIAsyncOutputStream)
|
|
.asyncWait(callback, 0, 0, mainThread);
|
|
inStream
|
|
.QueryInterface(Ci.nsIAsyncInputStream)
|
|
.asyncWait(callback, 0, 0, mainThread);
|
|
} catch (e) {
|
|
do_throw("asyncWait should not fail!");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* test_proxies_with_local_addresses
|
|
*
|
|
* Creates an nsISocketTransport and simulates a speculative connect request
|
|
* for a proxy that resolves to a local IP address.
|
|
* Runs asynchronously; on test success (i.e. failure to connect), the callback
|
|
* will call this function again until all proxies in the test list are done.
|
|
*
|
|
* Note: This test also uses an IP literal for the proxy. This should be ok,
|
|
* as the socket layer will ask for the proxy to be resolved anyway, and DNS
|
|
* code should return a numerical version of the address internally.
|
|
*/
|
|
function test_proxies_with_local_addresses() {
|
|
if (hostIdx >= localIPLiterals.length) {
|
|
// No more local IP addresses; move on.
|
|
next_test();
|
|
return;
|
|
}
|
|
var host = localIPLiterals[hostIdx++];
|
|
// Test another local IP address when the current one is done.
|
|
var next = test_proxies_with_local_addresses;
|
|
test_proxies(host, next);
|
|
}
|
|
|
|
class ProxyFilter {
|
|
constructor(type, host, port, flags) {
|
|
this._type = type;
|
|
this._host = host;
|
|
this._port = port;
|
|
this._flags = flags;
|
|
this.QueryInterface = ChromeUtils.generateQI(["nsIProtocolProxyFilter"]);
|
|
}
|
|
applyFilter(uri, pi, cb) {
|
|
const pps =
|
|
Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
|
|
cb.onProxyFilterResult(
|
|
pps.newProxyInfo(
|
|
this._type,
|
|
this._host,
|
|
this._port,
|
|
"",
|
|
"",
|
|
this._flags,
|
|
1000,
|
|
null
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
function test_speculative_connect_with_proxy_filter() {
|
|
let filter = new ProxyFilter("https", "localhost", 80, 0);
|
|
let pps = Cc["@mozilla.org/network/protocol-proxy-service;1"].getService();
|
|
pps.registerFilter(filter, 10);
|
|
let URI = ios.newURI("https://not-exist-dommain.com");
|
|
let principal = Services.scriptSecurityManager.createContentPrincipal(
|
|
URI,
|
|
{}
|
|
);
|
|
|
|
Assert.throws(
|
|
() =>
|
|
ios
|
|
.QueryInterface(Ci.nsISpeculativeConnect)
|
|
.speculativeConnect(URI, principal, null, false),
|
|
/NS_ERROR_FAILURE/,
|
|
"speculativeConnect should throw when no callback is provided and a proxy filter is registered"
|
|
);
|
|
pps.unregisterFilter(filter);
|
|
next_test();
|
|
}
|
|
|
|
/** next_test
|
|
*
|
|
* Calls the next test in testList. Each test is responsible for calling this
|
|
* function when its test cases are complete.
|
|
*/
|
|
function next_test() {
|
|
if (testIdx >= testList.length) {
|
|
// No more tests; we're done.
|
|
do_test_finished();
|
|
return;
|
|
}
|
|
info("SpeculativeConnect: " + testDescription[testIdx]);
|
|
hostIdx = 0;
|
|
// Start next test in list.
|
|
testList[testIdx++]();
|
|
}
|
|
|
|
/** run_test
|
|
*
|
|
* Main entry function for test execution.
|
|
*/
|
|
function run_test() {
|
|
ios = Services.io;
|
|
|
|
Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
|
|
registerCleanupFunction(() => {
|
|
Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
|
|
});
|
|
|
|
do_test_pending();
|
|
next_test();
|
|
}
|