Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | const CC = Components.Constructor; |
michael@0 | 2 | |
michael@0 | 3 | const ServerSocket = CC("@mozilla.org/network/server-socket;1", |
michael@0 | 4 | "nsIServerSocket", |
michael@0 | 5 | "init"); |
michael@0 | 6 | |
michael@0 | 7 | /** |
michael@0 | 8 | * TestServer: A single instance of this is created as |serv|. When created, |
michael@0 | 9 | * it starts listening on the loopback address on port |serv.port|. Tests will |
michael@0 | 10 | * connect to it after setting |serv.acceptCallback|, which is invoked after it |
michael@0 | 11 | * accepts a connection. |
michael@0 | 12 | * |
michael@0 | 13 | * Within |serv.acceptCallback|, various properties of |serv| can be used to |
michael@0 | 14 | * run checks. After the callback, the connection is closed, but the server |
michael@0 | 15 | * remains listening until |serv.stop| |
michael@0 | 16 | * |
michael@0 | 17 | * Note: TestServer can only handle a single connection at a time. Tests |
michael@0 | 18 | * should use run_next_test at the end of |serv.acceptCallback| to start the |
michael@0 | 19 | * following test which creates a connection. |
michael@0 | 20 | */ |
michael@0 | 21 | function TestServer() { |
michael@0 | 22 | this.reset(); |
michael@0 | 23 | |
michael@0 | 24 | // start server. |
michael@0 | 25 | // any port (-1), loopback only (true), default backlog (-1) |
michael@0 | 26 | this.listener = ServerSocket(-1, true, -1); |
michael@0 | 27 | this.port = this.listener.port; |
michael@0 | 28 | do_print('server: listening on', this.port); |
michael@0 | 29 | this.listener.asyncListen(this); |
michael@0 | 30 | } |
michael@0 | 31 | |
michael@0 | 32 | TestServer.prototype = { |
michael@0 | 33 | onSocketAccepted: function(socket, trans) { |
michael@0 | 34 | do_print('server: got client connection'); |
michael@0 | 35 | |
michael@0 | 36 | // one connection at a time. |
michael@0 | 37 | if (this.input !== null) { |
michael@0 | 38 | try { socket.close(); } catch(ignore) {} |
michael@0 | 39 | do_throw("Test written to handle one connection at a time."); |
michael@0 | 40 | } |
michael@0 | 41 | |
michael@0 | 42 | try { |
michael@0 | 43 | this.input = trans.openInputStream(0, 0, 0); |
michael@0 | 44 | this.output = trans.openOutputStream(0, 0, 0); |
michael@0 | 45 | this.selfAddr = trans.getScriptableSelfAddr(); |
michael@0 | 46 | this.peerAddr = trans.getScriptablePeerAddr(); |
michael@0 | 47 | |
michael@0 | 48 | this.acceptCallback(); |
michael@0 | 49 | } catch(e) { |
michael@0 | 50 | /* In a native callback such as onSocketAccepted, exceptions might not |
michael@0 | 51 | * get output correctly or logged to test output. Send them through |
michael@0 | 52 | * do_throw, which fails the test immediately. */ |
michael@0 | 53 | do_report_unexpected_exception(e, "in TestServer.onSocketAccepted"); |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | this.reset(); |
michael@0 | 57 | } , |
michael@0 | 58 | |
michael@0 | 59 | onStopListening: function(socket) {} , |
michael@0 | 60 | |
michael@0 | 61 | /** |
michael@0 | 62 | * Called to close a connection and clean up properties. |
michael@0 | 63 | */ |
michael@0 | 64 | reset: function() { |
michael@0 | 65 | if (this.input) |
michael@0 | 66 | try { this.input.close(); } catch(ignore) {} |
michael@0 | 67 | if (this.output) |
michael@0 | 68 | try { this.output.close(); } catch(ignore) {} |
michael@0 | 69 | |
michael@0 | 70 | this.input = null; |
michael@0 | 71 | this.output = null; |
michael@0 | 72 | this.acceptCallback = null; |
michael@0 | 73 | this.selfAddr = null; |
michael@0 | 74 | this.peerAddr = null; |
michael@0 | 75 | } , |
michael@0 | 76 | |
michael@0 | 77 | /** |
michael@0 | 78 | * Cleanup for TestServer and this test case. |
michael@0 | 79 | */ |
michael@0 | 80 | stop: function() { |
michael@0 | 81 | this.reset(); |
michael@0 | 82 | try { this.listener.close(); } catch(ignore) {} |
michael@0 | 83 | } |
michael@0 | 84 | }; |
michael@0 | 85 | |
michael@0 | 86 | |
michael@0 | 87 | /** |
michael@0 | 88 | * Helper function. |
michael@0 | 89 | * Compares two nsINetAddr objects and ensures they are logically equivalent. |
michael@0 | 90 | */ |
michael@0 | 91 | function checkAddrEqual(lhs, rhs) { |
michael@0 | 92 | do_check_eq(lhs.family, rhs.family); |
michael@0 | 93 | |
michael@0 | 94 | if (lhs.family === Ci.nsINetAddr.FAMILY_INET) { |
michael@0 | 95 | do_check_eq(lhs.address, rhs.address); |
michael@0 | 96 | do_check_eq(lhs.port, rhs.port); |
michael@0 | 97 | } |
michael@0 | 98 | |
michael@0 | 99 | /* TODO: fully support ipv6 and local */ |
michael@0 | 100 | } |
michael@0 | 101 | |
michael@0 | 102 | |
michael@0 | 103 | /** |
michael@0 | 104 | * An instance of SocketTransportService, used to create connections. |
michael@0 | 105 | */ |
michael@0 | 106 | var sts; |
michael@0 | 107 | |
michael@0 | 108 | /** |
michael@0 | 109 | * Single instance of TestServer |
michael@0 | 110 | */ |
michael@0 | 111 | var serv; |
michael@0 | 112 | |
michael@0 | 113 | /** |
michael@0 | 114 | * Connections have 5 seconds to be made, or a timeout function fails this |
michael@0 | 115 | * test. This prevents the test from hanging and bringing down the entire |
michael@0 | 116 | * xpcshell test chain. |
michael@0 | 117 | */ |
michael@0 | 118 | var connectTimeout = 5*1000; |
michael@0 | 119 | |
michael@0 | 120 | /** |
michael@0 | 121 | * A place for individual tests to place Objects of importance for access |
michael@0 | 122 | * throughout asynchronous testing. Particularly important for any output or |
michael@0 | 123 | * input streams opened, as cleanup of those objects (by the garbage collector) |
michael@0 | 124 | * causes the stream to close and may have other side effects. |
michael@0 | 125 | */ |
michael@0 | 126 | var testDataStore = null; |
michael@0 | 127 | |
michael@0 | 128 | /** |
michael@0 | 129 | * IPv4 test. |
michael@0 | 130 | */ |
michael@0 | 131 | function testIpv4() { |
michael@0 | 132 | testDataStore = { |
michael@0 | 133 | transport : null , |
michael@0 | 134 | ouput : null |
michael@0 | 135 | } |
michael@0 | 136 | |
michael@0 | 137 | serv.acceptCallback = function() { |
michael@0 | 138 | // disable the timeoutCallback |
michael@0 | 139 | serv.timeoutCallback = function(){}; |
michael@0 | 140 | |
michael@0 | 141 | var selfAddr = testDataStore.transport.getScriptableSelfAddr(); |
michael@0 | 142 | var peerAddr = testDataStore.transport.getScriptablePeerAddr(); |
michael@0 | 143 | |
michael@0 | 144 | // check peerAddr against expected values |
michael@0 | 145 | do_check_eq(peerAddr.family, Ci.nsINetAddr.FAMILY_INET); |
michael@0 | 146 | do_check_eq(peerAddr.port, testDataStore.transport.port); |
michael@0 | 147 | do_check_eq(peerAddr.port, serv.port); |
michael@0 | 148 | do_check_eq(peerAddr.address, "127.0.0.1"); |
michael@0 | 149 | |
michael@0 | 150 | // check selfAddr against expected values |
michael@0 | 151 | do_check_eq(selfAddr.family, Ci.nsINetAddr.FAMILY_INET); |
michael@0 | 152 | do_check_eq(selfAddr.address, "127.0.0.1"); |
michael@0 | 153 | |
michael@0 | 154 | // check that selfAddr = server.peerAddr and vice versa. |
michael@0 | 155 | checkAddrEqual(selfAddr, serv.peerAddr); |
michael@0 | 156 | checkAddrEqual(peerAddr, serv.selfAddr); |
michael@0 | 157 | |
michael@0 | 158 | testDataStore = null; |
michael@0 | 159 | do_execute_soon(run_next_test); |
michael@0 | 160 | }; |
michael@0 | 161 | |
michael@0 | 162 | // Useful timeout for debugging test hangs |
michael@0 | 163 | /*serv.timeoutCallback = function(tname) { |
michael@0 | 164 | if (tname === 'testIpv4') |
michael@0 | 165 | do_throw('testIpv4 never completed a connection to TestServ'); |
michael@0 | 166 | }; |
michael@0 | 167 | do_timeout(connectTimeout, function(){ serv.timeoutCallback('testIpv4'); });*/ |
michael@0 | 168 | |
michael@0 | 169 | testDataStore.transport = sts.createTransport(null, 0, '127.0.0.1', serv.port, null); |
michael@0 | 170 | /* |
michael@0 | 171 | * Need to hold |output| so that the output stream doesn't close itself and |
michael@0 | 172 | * the associated connection. |
michael@0 | 173 | */ |
michael@0 | 174 | testDataStore.output = testDataStore.transport.openOutputStream(Ci.nsITransport.OPEN_BLOCKING,0,0); |
michael@0 | 175 | |
michael@0 | 176 | /* NEXT: |
michael@0 | 177 | * openOutputStream -> onSocketAccepted -> acceptedCallback -> run_next_test |
michael@0 | 178 | * OR (if the above timeout is uncommented) |
michael@0 | 179 | * <connectTimeout lapses> -> timeoutCallback -> do_throw |
michael@0 | 180 | */ |
michael@0 | 181 | } |
michael@0 | 182 | |
michael@0 | 183 | |
michael@0 | 184 | /** |
michael@0 | 185 | * Running the tests. |
michael@0 | 186 | */ |
michael@0 | 187 | function run_test() { |
michael@0 | 188 | sts = Cc["@mozilla.org/network/socket-transport-service;1"] |
michael@0 | 189 | .getService(Ci.nsISocketTransportService); |
michael@0 | 190 | serv = new TestServer(); |
michael@0 | 191 | |
michael@0 | 192 | do_register_cleanup(function(){ serv.stop(); }); |
michael@0 | 193 | |
michael@0 | 194 | add_test(testIpv4); |
michael@0 | 195 | /* TODO: testIpv6 */ |
michael@0 | 196 | /* TODO: testLocal */ |
michael@0 | 197 | |
michael@0 | 198 | run_next_test(); |
michael@0 | 199 | } |