|
1 /* -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* vim: set ts=4 sts=4 et sw=4 tw=80: */ |
|
3 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 const CC = Components.Constructor; |
|
8 const ServerSocket = CC("@mozilla.org/network/server-socket;1", |
|
9 "nsIServerSocket", |
|
10 "init"); |
|
11 var serv; |
|
12 var ios; |
|
13 |
|
14 /** Example local IP addresses (literal IP address hostname). |
|
15 * |
|
16 * Note: for IPv6 Unique Local and Link Local, a wider range of addresses is |
|
17 * set aside than those most commonly used. Technically, link local addresses |
|
18 * include those beginning with fe80:: through febf::, although in practise |
|
19 * only fe80:: is used. Necko code blocks speculative connections for the wider |
|
20 * range; hence, this test considers that range too. |
|
21 */ |
|
22 var localIPv4Literals = |
|
23 [ // IPv4 RFC1918 \ |
|
24 "10.0.0.1", "10.10.10.10", "10.255.255.255", // 10/8 |
|
25 "172.16.0.1", "172.23.172.12", "172.31.255.255", // 172.16/20 |
|
26 "192.168.0.1", "192.168.192.168", "192.168.255.255", // 192.168/16 |
|
27 // IPv4 Link Local |
|
28 "169.254.0.1", "169.254.192.154", "169.254.255.255" // 169.254/16 |
|
29 ]; |
|
30 var localIPv6Literals = |
|
31 [ // IPv6 Unique Local fc00::/7 |
|
32 "fc00::1", "fdfe:dcba:9876:abcd:ef01:2345:6789:abcd", |
|
33 "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", |
|
34 // IPv6 Link Local fe80::/10 |
|
35 "fe80::1", "fe80::abcd:ef01:2345:6789", |
|
36 "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff" |
|
37 ]; |
|
38 var localIPLiterals = localIPv4Literals.concat(localIPv6Literals); |
|
39 |
|
40 /** Test function list and descriptions. |
|
41 */ |
|
42 var testList = |
|
43 [ test_speculative_connect, |
|
44 test_hostnames_resolving_to_local_addresses, |
|
45 test_proxies_with_local_addresses |
|
46 ]; |
|
47 |
|
48 var testDescription = |
|
49 [ "Expect pass with localhost", |
|
50 "Expect failure with resolved local IPs", |
|
51 "Expect failure for proxies with local IPs" |
|
52 ]; |
|
53 |
|
54 var testIdx = 0; |
|
55 var hostIdx = 0; |
|
56 |
|
57 |
|
58 /** TestServer |
|
59 * |
|
60 * Implements nsIServerSocket for test_speculative_connect. |
|
61 */ |
|
62 function TestServer() { |
|
63 this.listener = ServerSocket(-1, true, -1); |
|
64 this.listener.asyncListen(this); |
|
65 } |
|
66 |
|
67 TestServer.prototype = { |
|
68 QueryInterface: function(iid) { |
|
69 if (iid.equals(Ci.nsIServerSocket) || |
|
70 iid.equals(Ci.nsISupports)) |
|
71 return this; |
|
72 throw Cr.NS_ERROR_NO_INTERFACE; |
|
73 }, |
|
74 onSocketAccepted: function(socket, trans) { |
|
75 try { this.listener.close(); } catch(e) {} |
|
76 do_check_true(true); |
|
77 next_test(); |
|
78 }, |
|
79 |
|
80 onStopListening: function(socket) {} |
|
81 }; |
|
82 |
|
83 /** TestOutputStreamCallback |
|
84 * |
|
85 * Implements nsIOutputStreamCallback for socket layer tests. |
|
86 */ |
|
87 function TestOutputStreamCallback(transport, hostname, proxied, expectSuccess, next) { |
|
88 this.transport = transport; |
|
89 this.hostname = hostname; |
|
90 this.proxied = proxied; |
|
91 this.expectSuccess = expectSuccess; |
|
92 this.next = next; |
|
93 this.dummyContent = "Dummy content"; |
|
94 } |
|
95 |
|
96 TestOutputStreamCallback.prototype = { |
|
97 QueryInterface: function(iid) { |
|
98 if (iid.equals(Ci.nsIOutputStreamCallback) || |
|
99 iid.equals(Ci.nsISupports)) |
|
100 return this; |
|
101 throw Cr.NS_ERROR_NO_INTERFACE; |
|
102 }, |
|
103 onOutputStreamReady: function(stream) { |
|
104 do_check_neq(typeof(stream), undefined); |
|
105 try { |
|
106 stream.write(this.dummyContent, this.dummyContent.length); |
|
107 } catch (e) { |
|
108 // Spec Connect FAILED. |
|
109 do_check_instanceof(e, Ci.nsIException); |
|
110 if (this.expectSuccess) { |
|
111 // We may expect success, but the address could be unreachable |
|
112 // in the test environment, so expect errors. |
|
113 if (this.proxied) { |
|
114 do_check_true(e.result == Cr.NS_ERROR_NET_TIMEOUT || |
|
115 e.result == Cr.NS_ERROR_PROXY_CONNECTION_REFUSED); |
|
116 } else { |
|
117 do_check_true(e.result == Cr.NS_ERROR_NET_TIMEOUT || |
|
118 e.result == Cr.NS_ERROR_CONNECTION_REFUSED); |
|
119 } |
|
120 } else { |
|
121 // A refusal to connect speculatively should throw an error. |
|
122 do_check_eq(e.result, Cr.NS_ERROR_CONNECTION_REFUSED); |
|
123 } |
|
124 this.transport.close(Cr.NS_BINDING_ABORTED); |
|
125 this.next(); |
|
126 return; |
|
127 } |
|
128 // Spec Connect SUCCEEDED. |
|
129 if (this.expectSuccess) { |
|
130 do_check_true(true, "Success for " + this.hostname); |
|
131 } else { |
|
132 do_throw("Speculative Connect should have failed for " + |
|
133 this.hostname); |
|
134 } |
|
135 this.transport.close(Cr.NS_BINDING_ABORTED); |
|
136 this.next(); |
|
137 } |
|
138 }; |
|
139 |
|
140 /** test_speculative_connect |
|
141 * |
|
142 * Tests a basic positive case using nsIOService.SpeculativeConnect: |
|
143 * connecting to localhost. |
|
144 */ |
|
145 function test_speculative_connect() { |
|
146 serv = new TestServer(); |
|
147 var URI = ios.newURI("http://localhost:" + serv.listener.port + "/just/a/test", null, null); |
|
148 ios.QueryInterface(Ci.nsISpeculativeConnect) |
|
149 .speculativeConnect(URI, null); |
|
150 } |
|
151 |
|
152 /* Speculative connections should not be allowed for hosts with local IP |
|
153 * addresses (Bug 853423). That list includes: |
|
154 * -- IPv4 RFC1918 and Link Local Addresses. |
|
155 * -- IPv6 Unique and Link Local Addresses. |
|
156 * |
|
157 * Two tests are required: |
|
158 * 1. Verify IP Literals passed to the SpeculativeConnect API. |
|
159 * 2. Verify hostnames that need to be resolved at the socket layer. |
|
160 */ |
|
161 |
|
162 /** test_hostnames_resolving_to_addresses |
|
163 * |
|
164 * Common test function for resolved hostnames. Takes a list of hosts, a |
|
165 * boolean to determine if the test is expected to succeed or fail, and a |
|
166 * function to call the next test case. |
|
167 */ |
|
168 function test_hostnames_resolving_to_addresses(host, expectSuccess, next) { |
|
169 do_print(host); |
|
170 var sts = Cc["@mozilla.org/network/socket-transport-service;1"] |
|
171 .getService(Ci.nsISocketTransportService); |
|
172 do_check_neq(typeof(sts), undefined); |
|
173 var transport = sts.createTransport(null, 0, host, 80, null); |
|
174 do_check_neq(typeof(transport), undefined); |
|
175 |
|
176 transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918; |
|
177 transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1); |
|
178 transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1); |
|
179 do_check_eq(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT)); |
|
180 |
|
181 var outStream = transport.openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED,0,0); |
|
182 do_check_neq(typeof(outStream), undefined); |
|
183 |
|
184 var callback = new TestOutputStreamCallback(transport, host, false, |
|
185 expectSuccess, |
|
186 next); |
|
187 do_check_neq(typeof(callback), undefined); |
|
188 |
|
189 // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait |
|
190 // adds callback to nsOutputStreamReadyEvent on main thread, and doesn't |
|
191 // addref off the main thread. |
|
192 var gThreadManager = Cc["@mozilla.org/thread-manager;1"] |
|
193 .getService(Ci.nsIThreadManager); |
|
194 var mainThread = gThreadManager.currentThread; |
|
195 |
|
196 try { |
|
197 outStream.QueryInterface(Ci.nsIAsyncOutputStream) |
|
198 .asyncWait(callback, 0, 0, mainThread); |
|
199 } catch (e) { |
|
200 do_throw("asyncWait should not fail!"); |
|
201 } |
|
202 } |
|
203 |
|
204 /** |
|
205 * test_hostnames_resolving_to_local_addresses |
|
206 * |
|
207 * Creates an nsISocketTransport and simulates a speculative connect request |
|
208 * for a hostname that resolves to a local IP address. |
|
209 * Runs asynchronously; on test success (i.e. failure to connect), the callback |
|
210 * will call this function again until all hostnames in the test list are done. |
|
211 * |
|
212 * Note: This test also uses an IP literal for the hostname. This should be ok, |
|
213 * as the socket layer will ask for the hostname to be resolved anyway, and DNS |
|
214 * code should return a numerical version of the address internally. |
|
215 */ |
|
216 function test_hostnames_resolving_to_local_addresses() { |
|
217 if (hostIdx >= localIPLiterals.length) { |
|
218 // No more local IP addresses; move on. |
|
219 next_test(); |
|
220 return; |
|
221 } |
|
222 var host = localIPLiterals[hostIdx++]; |
|
223 // Test another local IP address when the current one is done. |
|
224 var next = test_hostnames_resolving_to_local_addresses; |
|
225 test_hostnames_resolving_to_addresses(host, false, next); |
|
226 } |
|
227 |
|
228 /** test_speculative_connect_with_host_list |
|
229 * |
|
230 * Common test function for resolved proxy hosts. Takes a list of hosts, a |
|
231 * boolean to determine if the test is expected to succeed or fail, and a |
|
232 * function to call the next test case. |
|
233 */ |
|
234 function test_proxies(proxyHost, expectSuccess, next) { |
|
235 do_print("Proxy: " + proxyHost); |
|
236 var sts = Cc["@mozilla.org/network/socket-transport-service;1"] |
|
237 .getService(Ci.nsISocketTransportService); |
|
238 do_check_neq(typeof(sts), undefined); |
|
239 var pps = Cc["@mozilla.org/network/protocol-proxy-service;1"] |
|
240 .getService(); |
|
241 do_check_neq(typeof(pps), undefined); |
|
242 |
|
243 var proxyInfo = pps.newProxyInfo("http", proxyHost, 8080, 0, 1, null); |
|
244 do_check_neq(typeof(proxyInfo), undefined); |
|
245 |
|
246 var transport = sts.createTransport(null, 0, "dummyHost", 80, proxyInfo); |
|
247 do_check_neq(typeof(transport), undefined); |
|
248 |
|
249 transport.connectionFlags = Ci.nsISocketTransport.DISABLE_RFC1918; |
|
250 |
|
251 transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 1); |
|
252 do_check_eq(1, transport.getTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT)); |
|
253 transport.setTimeout(Ci.nsISocketTransport.TIMEOUT_READ_WRITE, 1); |
|
254 |
|
255 var outStream = transport.openOutputStream(Ci.nsITransport.OPEN_UNBUFFERED,0,0); |
|
256 do_check_neq(typeof(outStream), undefined); |
|
257 |
|
258 var callback = new TestOutputStreamCallback(transport, proxyHost, true, |
|
259 expectSuccess, |
|
260 next); |
|
261 do_check_neq(typeof(callback), undefined); |
|
262 |
|
263 // Need to get main thread pointer to ensure nsSocketTransport::AsyncWait |
|
264 // adds callback to nsOutputStreamReadyEvent on main thread, and doesn't |
|
265 // addref off the main thread. |
|
266 var gThreadManager = Cc["@mozilla.org/thread-manager;1"] |
|
267 .getService(Ci.nsIThreadManager); |
|
268 var mainThread = gThreadManager.currentThread; |
|
269 |
|
270 try { |
|
271 outStream.QueryInterface(Ci.nsIAsyncOutputStream) |
|
272 .asyncWait(callback, 0, 0, mainThread); |
|
273 } catch (e) { |
|
274 do_throw("asyncWait should not fail!"); |
|
275 } |
|
276 } |
|
277 |
|
278 /** |
|
279 * test_proxies_with_local_addresses |
|
280 * |
|
281 * Creates an nsISocketTransport and simulates a speculative connect request |
|
282 * for a proxy that resolves to a local IP address. |
|
283 * Runs asynchronously; on test success (i.e. failure to connect), the callback |
|
284 * will call this function again until all proxies in the test list are done. |
|
285 * |
|
286 * Note: This test also uses an IP literal for the proxy. This should be ok, |
|
287 * as the socket layer will ask for the proxy to be resolved anyway, and DNS |
|
288 * code should return a numerical version of the address internally. |
|
289 */ |
|
290 function test_proxies_with_local_addresses() { |
|
291 if (hostIdx >= localIPLiterals.length) { |
|
292 // No more local IP addresses; move on. |
|
293 next_test(); |
|
294 return; |
|
295 } |
|
296 var host = localIPLiterals[hostIdx++]; |
|
297 // Test another local IP address when the current one is done. |
|
298 var next = test_proxies_with_local_addresses; |
|
299 test_proxies(host, false, next); |
|
300 } |
|
301 |
|
302 /** next_test |
|
303 * |
|
304 * Calls the next test in testList. Each test is responsible for calling this |
|
305 * function when its test cases are complete. |
|
306 */ |
|
307 function next_test() { |
|
308 if (testIdx >= testList.length) { |
|
309 // No more tests; we're done. |
|
310 do_test_finished(); |
|
311 return; |
|
312 } |
|
313 do_print("SpeculativeConnect: " + testDescription[testIdx]); |
|
314 hostIdx = 0; |
|
315 // Start next test in list. |
|
316 testList[testIdx++](); |
|
317 } |
|
318 |
|
319 /** run_test |
|
320 * |
|
321 * Main entry function for test execution. |
|
322 */ |
|
323 function run_test() { |
|
324 ios = Cc["@mozilla.org/network/io-service;1"] |
|
325 .getService(Ci.nsIIOService); |
|
326 |
|
327 do_test_pending(); |
|
328 next_test(); |
|
329 } |
|
330 |