michael@0: /* -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim: set ts=4 sts=4 et sw=4 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const CC = Components.Constructor; michael@0: const ServerSocket = CC("@mozilla.org/network/server-socket;1", michael@0: "nsIServerSocket", michael@0: "init"); michael@0: var serv; michael@0: var ios; michael@0: michael@0: /** Example local IP addresses (literal IP address hostname). michael@0: * michael@0: * Note: for IPv6 Unique Local and Link Local, a wider range of addresses is michael@0: * set aside than those most commonly used. Technically, link local addresses michael@0: * include those beginning with fe80:: through febf::, although in practise michael@0: * only fe80:: is used. Necko code blocks speculative connections for the wider michael@0: * range; hence, this test considers that range too. michael@0: */ michael@0: var localIPv4Literals = michael@0: [ // IPv4 RFC1918 \ michael@0: "10.0.0.1", "10.10.10.10", "10.255.255.255", // 10/8 michael@0: "172.16.0.1", "172.23.172.12", "172.31.255.255", // 172.16/20 michael@0: "192.168.0.1", "192.168.192.168", "192.168.255.255", // 192.168/16 michael@0: // IPv4 Link Local michael@0: "169.254.0.1", "169.254.192.154", "169.254.255.255" // 169.254/16 michael@0: ]; michael@0: var localIPv6Literals = michael@0: [ // IPv6 Unique Local fc00::/7 michael@0: "fc00::1", "fdfe:dcba:9876:abcd:ef01:2345:6789:abcd", michael@0: "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", michael@0: // IPv6 Link Local fe80::/10 michael@0: "fe80::1", "fe80::abcd:ef01:2345:6789", michael@0: "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff" michael@0: ]; michael@0: var localIPLiterals = localIPv4Literals.concat(localIPv6Literals); michael@0: michael@0: /** Test function list and descriptions. michael@0: */ michael@0: var testList = michael@0: [ test_speculative_connect, michael@0: test_hostnames_resolving_to_local_addresses, michael@0: test_proxies_with_local_addresses michael@0: ]; michael@0: michael@0: var testDescription = michael@0: [ "Expect pass with localhost", michael@0: "Expect failure with resolved local IPs", michael@0: "Expect failure for proxies with local IPs" michael@0: ]; michael@0: michael@0: var testIdx = 0; michael@0: var hostIdx = 0; michael@0: michael@0: michael@0: /** TestServer michael@0: * michael@0: * Implements nsIServerSocket for test_speculative_connect. michael@0: */ michael@0: function TestServer() { michael@0: this.listener = ServerSocket(-1, true, -1); michael@0: this.listener.asyncListen(this); michael@0: } michael@0: michael@0: TestServer.prototype = { michael@0: QueryInterface: function(iid) { michael@0: if (iid.equals(Ci.nsIServerSocket) || michael@0: iid.equals(Ci.nsISupports)) michael@0: return this; michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: onSocketAccepted: function(socket, trans) { michael@0: try { this.listener.close(); } catch(e) {} michael@0: do_check_true(true); michael@0: next_test(); michael@0: }, michael@0: michael@0: onStopListening: function(socket) {} michael@0: }; michael@0: michael@0: /** TestOutputStreamCallback michael@0: * michael@0: * Implements nsIOutputStreamCallback for socket layer tests. michael@0: */ michael@0: function TestOutputStreamCallback(transport, hostname, proxied, expectSuccess, next) { michael@0: this.transport = transport; michael@0: this.hostname = hostname; michael@0: this.proxied = proxied; michael@0: this.expectSuccess = expectSuccess; michael@0: this.next = next; michael@0: this.dummyContent = "Dummy content"; michael@0: } michael@0: michael@0: TestOutputStreamCallback.prototype = { michael@0: QueryInterface: function(iid) { michael@0: if (iid.equals(Ci.nsIOutputStreamCallback) || michael@0: iid.equals(Ci.nsISupports)) michael@0: return this; michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: onOutputStreamReady: function(stream) { michael@0: do_check_neq(typeof(stream), undefined); michael@0: try { michael@0: stream.write(this.dummyContent, this.dummyContent.length); michael@0: } catch (e) { michael@0: // Spec Connect FAILED. michael@0: do_check_instanceof(e, Ci.nsIException); michael@0: if (this.expectSuccess) { michael@0: // We may expect success, but the address could be unreachable michael@0: // in the test environment, so expect errors. michael@0: if (this.proxied) { michael@0: do_check_true(e.result == Cr.NS_ERROR_NET_TIMEOUT || michael@0: e.result == Cr.NS_ERROR_PROXY_CONNECTION_REFUSED); michael@0: } else { michael@0: do_check_true(e.result == Cr.NS_ERROR_NET_TIMEOUT || michael@0: e.result == Cr.NS_ERROR_CONNECTION_REFUSED); michael@0: } michael@0: } else { michael@0: // A refusal to connect speculatively should throw an error. michael@0: do_check_eq(e.result, Cr.NS_ERROR_CONNECTION_REFUSED); michael@0: } michael@0: this.transport.close(Cr.NS_BINDING_ABORTED); michael@0: this.next(); michael@0: return; michael@0: } michael@0: // Spec Connect SUCCEEDED. michael@0: if (this.expectSuccess) { michael@0: do_check_true(true, "Success for " + this.hostname); michael@0: } else { michael@0: do_throw("Speculative Connect should have failed for " + michael@0: this.hostname); michael@0: } michael@0: this.transport.close(Cr.NS_BINDING_ABORTED); michael@0: this.next(); michael@0: } michael@0: }; michael@0: michael@0: /** test_speculative_connect michael@0: * michael@0: * Tests a basic positive case using nsIOService.SpeculativeConnect: michael@0: * connecting to localhost. michael@0: */ michael@0: function test_speculative_connect() { michael@0: serv = new TestServer(); michael@0: var URI = ios.newURI("http://localhost:" + serv.listener.port + "/just/a/test", null, null); michael@0: ios.QueryInterface(Ci.nsISpeculativeConnect) michael@0: .speculativeConnect(URI, null); michael@0: } michael@0: michael@0: /* Speculative connections should not be allowed for hosts with local IP michael@0: * addresses (Bug 853423). That list includes: michael@0: * -- IPv4 RFC1918 and Link Local Addresses. michael@0: * -- IPv6 Unique and Link Local Addresses. michael@0: * michael@0: * Two tests are required: michael@0: * 1. Verify IP Literals passed to the SpeculativeConnect API. michael@0: * 2. Verify hostnames that need to be resolved at the socket layer. michael@0: */ michael@0: michael@0: /** test_hostnames_resolving_to_addresses michael@0: * michael@0: * Common test function for resolved hostnames. Takes a list of hosts, a michael@0: * boolean to determine if the test is expected to succeed or fail, and a michael@0: * function to call the next test case. michael@0: */ michael@0: function test_hostnames_resolving_to_addresses(host, expectSuccess, next) { michael@0: do_print(host); michael@0: var sts = Cc["@mozilla.org/network/socket-transport-service;1"] michael@0: .getService(Ci.nsISocketTransportService); michael@0: do_check_neq(typeof(sts), undefined); michael@0: var transport = sts.createTransport(null, 0, host, 80, null); michael@0: do_check_neq(typeof(transport), undefined); michael@0: michael@0: transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918; michael@0: transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1); michael@0: transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1); michael@0: do_check_eq(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT)); michael@0: michael@0: var outStream = transport.openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED,0,0); michael@0: do_check_neq(typeof(outStream), undefined); michael@0: michael@0: var callback = new TestOutputStreamCallback(transport, host, false, michael@0: expectSuccess, michael@0: next); michael@0: do_check_neq(typeof(callback), undefined); michael@0: michael@0: // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait michael@0: // adds callback to nsOutputStreamReadyEvent on main thread, and doesn't michael@0: // addref off the main thread. michael@0: var gThreadManager = Cc["@mozilla.org/thread-manager;1"] michael@0: .getService(Ci.nsIThreadManager); michael@0: var mainThread = gThreadManager.currentThread; michael@0: michael@0: try { michael@0: outStream.QueryInterface(Ci.nsIAsyncOutputStream) michael@0: .asyncWait(callback, 0, 0, mainThread); michael@0: } catch (e) { michael@0: do_throw("asyncWait should not fail!"); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * test_hostnames_resolving_to_local_addresses michael@0: * michael@0: * Creates an nsISocketTransport and simulates a speculative connect request michael@0: * for a hostname that resolves to a local IP address. michael@0: * Runs asynchronously; on test success (i.e. failure to connect), the callback michael@0: * will call this function again until all hostnames in the test list are done. michael@0: * michael@0: * Note: This test also uses an IP literal for the hostname. This should be ok, michael@0: * as the socket layer will ask for the hostname to be resolved anyway, and DNS michael@0: * code should return a numerical version of the address internally. michael@0: */ michael@0: function test_hostnames_resolving_to_local_addresses() { michael@0: if (hostIdx >= localIPLiterals.length) { michael@0: // No more local IP addresses; move on. michael@0: next_test(); michael@0: return; michael@0: } michael@0: var host = localIPLiterals[hostIdx++]; michael@0: // Test another local IP address when the current one is done. michael@0: var next = test_hostnames_resolving_to_local_addresses; michael@0: test_hostnames_resolving_to_addresses(host, false, next); michael@0: } michael@0: michael@0: /** test_speculative_connect_with_host_list michael@0: * michael@0: * Common test function for resolved proxy hosts. Takes a list of hosts, a michael@0: * boolean to determine if the test is expected to succeed or fail, and a michael@0: * function to call the next test case. michael@0: */ michael@0: function test_proxies(proxyHost, expectSuccess, next) { michael@0: do_print("Proxy: " + proxyHost); michael@0: var sts = Cc["@mozilla.org/network/socket-transport-service;1"] michael@0: .getService(Ci.nsISocketTransportService); michael@0: do_check_neq(typeof(sts), undefined); michael@0: var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"] michael@0: .getService(); michael@0: do_check_neq(typeof(pps), undefined); michael@0: michael@0: var proxyInfo = pps.newProxyInfo("http", proxyHost, 8080, 0, 1, null); michael@0: do_check_neq(typeof(proxyInfo), undefined); michael@0: michael@0: var transport = sts.createTransport(null, 0, "dummyHost", 80, proxyInfo); michael@0: do_check_neq(typeof(transport), undefined); michael@0: michael@0: transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918; michael@0: michael@0: transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1); michael@0: do_check_eq(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT)); michael@0: transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1); michael@0: michael@0: var outStream = transport.openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED,0,0); michael@0: do_check_neq(typeof(outStream), undefined); michael@0: michael@0: var callback = new TestOutputStreamCallback(transport, proxyHost, true, michael@0: expectSuccess, michael@0: next); michael@0: do_check_neq(typeof(callback), undefined); michael@0: michael@0: // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait michael@0: // adds callback to nsOutputStreamReadyEvent on main thread, and doesn't michael@0: // addref off the main thread. michael@0: var gThreadManager = Cc["@mozilla.org/thread-manager;1"] michael@0: .getService(Ci.nsIThreadManager); michael@0: var mainThread = gThreadManager.currentThread; michael@0: michael@0: try { michael@0: outStream.QueryInterface(Ci.nsIAsyncOutputStream) michael@0: .asyncWait(callback, 0, 0, mainThread); michael@0: } catch (e) { michael@0: do_throw("asyncWait should not fail!"); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * test_proxies_with_local_addresses michael@0: * michael@0: * Creates an nsISocketTransport and simulates a speculative connect request michael@0: * for a proxy that resolves to a local IP address. michael@0: * Runs asynchronously; on test success (i.e. failure to connect), the callback michael@0: * will call this function again until all proxies in the test list are done. michael@0: * michael@0: * Note: This test also uses an IP literal for the proxy. This should be ok, michael@0: * as the socket layer will ask for the proxy to be resolved anyway, and DNS michael@0: * code should return a numerical version of the address internally. michael@0: */ michael@0: function test_proxies_with_local_addresses() { michael@0: if (hostIdx >= localIPLiterals.length) { michael@0: // No more local IP addresses; move on. michael@0: next_test(); michael@0: return; michael@0: } michael@0: var host = localIPLiterals[hostIdx++]; michael@0: // Test another local IP address when the current one is done. michael@0: var next = test_proxies_with_local_addresses; michael@0: test_proxies(host, false, next); michael@0: } michael@0: michael@0: /** next_test michael@0: * michael@0: * Calls the next test in testList. Each test is responsible for calling this michael@0: * function when its test cases are complete. michael@0: */ michael@0: function next_test() { michael@0: if (testIdx >= testList.length) { michael@0: // No more tests; we're done. michael@0: do_test_finished(); michael@0: return; michael@0: } michael@0: do_print("SpeculativeConnect: " + testDescription[testIdx]); michael@0: hostIdx = 0; michael@0: // Start next test in list. michael@0: testList[testIdx++](); michael@0: } michael@0: michael@0: /** run_test michael@0: * michael@0: * Main entry function for test execution. michael@0: */ michael@0: function run_test() { michael@0: ios = Cc["@mozilla.org/network/io-service;1"] michael@0: .getService(Ci.nsIIOService); michael@0: michael@0: do_test_pending(); michael@0: next_test(); michael@0: } michael@0: