/* -*- 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, ]; 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", ]; 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(socket, trans) { try { this.listener.close(); } catch (e) {} Assert.ok(true); next_test(); }, onStopListening(socket) {}, }; /** 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); } /** 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(); }