test_proxyconnect.js (9803B)
1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 // test_connectonly tests happy path of proxy connect 7 // 1. CONNECT to localhost:socketserver_port 8 // 2. Write 200 Connection established 9 // 3. Write data to the tunnel (and read server-side) 10 // 4. Read data from the tunnel (and write server-side) 11 // 5. done 12 // test_connectonly_noproxy tests an http channel with only connect set but 13 // no proxy configured. 14 // 1. OnTransportAvailable callback NOT called (checked in step 2) 15 // 2. StopRequest callback called 16 // 3. done 17 // test_connectonly_nonhttp tests an http channel with only connect set with a 18 // non-http proxy. 19 // 1. OnTransportAvailable callback NOT called (checked in step 2) 20 // 2. StopRequest callback called 21 // 3. done 22 23 // -1 then initialized with an actual port from the serversocket 24 "use strict"; 25 26 var socketserver_port = -1; 27 28 const CC = Components.Constructor; 29 const ServerSocket = CC( 30 "@mozilla.org/network/server-socket;1", 31 "nsIServerSocket", 32 "init" 33 ); 34 const BinaryInputStream = CC( 35 "@mozilla.org/binaryinputstream;1", 36 "nsIBinaryInputStream", 37 "setInputStream" 38 ); 39 const BinaryOutputStream = CC( 40 "@mozilla.org/binaryoutputstream;1", 41 "nsIBinaryOutputStream", 42 "setOutputStream" 43 ); 44 45 const STATE_NONE = 0; 46 const STATE_READ_CONNECT_REQUEST = 1; 47 const STATE_WRITE_CONNECTION_ESTABLISHED = 2; 48 const STATE_CHECK_WRITE = 3; // write to the tunnel 49 const STATE_CHECK_WRITE_READ = 4; // wrote to the tunnel, check connection data 50 const STATE_CHECK_READ = 5; // read from the tunnel 51 const STATE_CHECK_READ_WROTE = 6; // wrote to connection, check tunnel data 52 const STATE_COMPLETED = 100; 53 54 const CONNECT_RESPONSE_STRING = "HTTP/1.1 200 Connection established\r\n\r\n"; 55 const CHECK_WRITE_STRING = "hello"; 56 const CHECK_READ_STRING = "world"; 57 const ALPN = "webrtc"; 58 59 var connectRequest = ""; 60 var checkWriteData = ""; 61 var checkReadData = ""; 62 63 var threadManager; 64 var socket; 65 var streamIn; 66 var streamOut; 67 var accepted = false; 68 var acceptedSocket; 69 var state = STATE_NONE; 70 var transportAvailable = false; 71 var proxiedChannel; 72 var listener = { 73 expectedCode: -1, // uninitialized 74 75 onStartRequest: function test_onStartR() {}, 76 77 onDataAvailable: function test_ODA() { 78 do_throw("Should not get any data!"); 79 }, 80 81 onStopRequest: function test_onStopR(request, status) { 82 if (state === STATE_COMPLETED) { 83 Assert.equal(transportAvailable, false, "transport available not called"); 84 Assert.equal(status, 0x80004005, "error code matches"); 85 Assert.equal(proxiedChannel.httpProxyConnectResponseCode, 200); 86 nextTest(); 87 return; 88 } 89 90 Assert.equal(accepted, true, "socket accepted"); 91 accepted = false; 92 }, 93 }; 94 95 var upgradeListener = { 96 onTransportAvailable: (transport, socketIn, socketOut) => { 97 if (!transport || !socketIn || !socketOut) { 98 do_throw("on transport available failed"); 99 } 100 101 if (state !== STATE_CHECK_WRITE) { 102 do_throw("bad state"); 103 } 104 105 transportAvailable = true; 106 107 socketIn.asyncWait(connectHandler, 0, 0, threadManager.mainThread); 108 socketOut.asyncWait(connectHandler, 0, 0, threadManager.mainThread); 109 }, 110 QueryInterface: ChromeUtils.generateQI(["nsIHttpUpgradeListener"]), 111 }; 112 113 var connectHandler = { 114 onInputStreamReady: input => { 115 try { 116 const bis = new BinaryInputStream(input); 117 var data = bis.readByteArray(input.available()); 118 119 dataAvailable(data); 120 121 if (state !== STATE_COMPLETED) { 122 input.asyncWait(connectHandler, 0, 0, threadManager.mainThread); 123 } 124 } catch (e) { 125 do_throw(e); 126 } 127 }, 128 onOutputStreamReady: output => { 129 writeData(output); 130 }, 131 QueryInterface: iid => { 132 if ( 133 iid.equals(Ci.nsISupports) || 134 iid.equals(Ci.nsIInputStreamCallback) || 135 iid.equals(Ci.nsIOutputStreamCallback) 136 ) { 137 return this; 138 } 139 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); 140 }, 141 }; 142 143 function dataAvailable(data) { 144 switch (state) { 145 case STATE_READ_CONNECT_REQUEST: { 146 connectRequest += String.fromCharCode.apply(String, data); 147 const headerEnding = connectRequest.indexOf("\r\n\r\n"); 148 const alpnHeaderIndex = connectRequest.indexOf(`ALPN: ${ALPN}`); 149 150 if (headerEnding != -1) { 151 const requestLine = `CONNECT localhost:${socketserver_port} HTTP/1.1`; 152 Assert.equal(connectRequest.indexOf(requestLine), 0, "connect request"); 153 Assert.equal(headerEnding, connectRequest.length - 4, "req head only"); 154 Assert.notEqual(alpnHeaderIndex, -1, "alpn header found"); 155 156 state = STATE_WRITE_CONNECTION_ESTABLISHED; 157 streamOut.asyncWait(connectHandler, 0, 0, threadManager.mainThread); 158 } 159 160 break; 161 } 162 case STATE_CHECK_WRITE_READ: 163 checkWriteData += String.fromCharCode.apply(String, data); 164 165 if (checkWriteData.length >= CHECK_WRITE_STRING.length) { 166 Assert.equal(checkWriteData, CHECK_WRITE_STRING, "correct write data"); 167 168 state = STATE_CHECK_READ; 169 streamOut.asyncWait(connectHandler, 0, 0, threadManager.mainThread); 170 } 171 172 break; 173 case STATE_CHECK_READ_WROTE: 174 checkReadData += String.fromCharCode.apply(String, data); 175 176 if (checkReadData.length >= CHECK_READ_STRING.length) { 177 Assert.equal(checkReadData, CHECK_READ_STRING, "correct read data"); 178 179 state = STATE_COMPLETED; 180 181 streamIn.asyncWait(null, 0, 0, null); 182 acceptedSocket.close(0); 183 184 nextTest(); 185 } 186 187 break; 188 default: 189 do_throw("bad state: " + state); 190 } 191 } 192 193 function writeData(output) { 194 let bos = new BinaryOutputStream(output); 195 196 switch (state) { 197 case STATE_WRITE_CONNECTION_ESTABLISHED: 198 bos.write(CONNECT_RESPONSE_STRING, CONNECT_RESPONSE_STRING.length); 199 state = STATE_CHECK_WRITE; 200 break; 201 case STATE_CHECK_READ: 202 bos.write(CHECK_READ_STRING, CHECK_READ_STRING.length); 203 state = STATE_CHECK_READ_WROTE; 204 break; 205 case STATE_CHECK_WRITE: 206 bos.write(CHECK_WRITE_STRING, CHECK_WRITE_STRING.length); 207 state = STATE_CHECK_WRITE_READ; 208 break; 209 default: 210 do_throw("bad state: " + state); 211 } 212 } 213 214 function makeChan(url) { 215 if (!url) { 216 url = "https://localhost:" + socketserver_port + "/"; 217 } 218 219 var flags = 220 Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL | 221 Ci.nsILoadInfo.SEC_DONT_FOLLOW_REDIRECTS | 222 Ci.nsILoadInfo.SEC_COOKIES_OMIT; 223 224 var chan = NetUtil.newChannel({ 225 uri: url, 226 loadUsingSystemPrincipal: true, 227 securityFlags: flags, 228 }); 229 chan = chan.QueryInterface(Ci.nsIHttpChannel); 230 231 var internal = chan.QueryInterface(Ci.nsIHttpChannelInternal); 232 internal.HTTPUpgrade(ALPN, upgradeListener); 233 internal.setConnectOnly(false); 234 235 return chan; 236 } 237 238 function socketAccepted(socket1, transport) { 239 accepted = true; 240 241 // copied from httpd.js 242 const SEGMENT_SIZE = 8192; 243 const SEGMENT_COUNT = 1024; 244 245 switch (state) { 246 case STATE_NONE: 247 state = STATE_READ_CONNECT_REQUEST; 248 break; 249 default: 250 return; 251 } 252 253 acceptedSocket = transport; 254 255 try { 256 streamIn = transport 257 .openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT) 258 .QueryInterface(Ci.nsIAsyncInputStream); 259 streamOut = transport 260 .openOutputStream(0, 0, 0) 261 .QueryInterface(Ci.nsIAsyncOutputStream); 262 263 streamIn.asyncWait(connectHandler, 0, 0, threadManager.mainThread); 264 } catch (e) { 265 transport.close(Cr.NS_BINDING_ABORTED); 266 do_throw(e); 267 } 268 } 269 270 function stopListening() { 271 if (tests && tests.length !== 0 && do_throw) { 272 do_throw("should never stop"); 273 } 274 } 275 276 function createProxy() { 277 try { 278 threadManager = Cc["@mozilla.org/thread-manager;1"].getService(); 279 280 socket = new ServerSocket(-1, true, 1); 281 socketserver_port = socket.port; 282 283 socket.asyncListen({ 284 onSocketAccepted: socketAccepted, 285 onStopListening: stopListening, 286 }); 287 } catch (e) { 288 do_throw(e); 289 } 290 } 291 292 function test_connectonly() { 293 Services.prefs.setCharPref("network.proxy.ssl", "localhost"); 294 Services.prefs.setIntPref("network.proxy.ssl_port", socketserver_port); 295 Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); 296 Services.prefs.setIntPref("network.proxy.type", 1); 297 298 var chan = makeChan(); 299 proxiedChannel = chan.QueryInterface(Ci.nsIProxiedChannel); 300 chan.asyncOpen(listener); 301 302 do_test_pending(); 303 } 304 305 function test_connectonly_noproxy() { 306 clearPrefs(); 307 var chan = makeChan(); 308 chan.asyncOpen(listener); 309 310 do_test_pending(); 311 } 312 313 function test_connectonly_nonhttp() { 314 clearPrefs(); 315 316 Services.prefs.setCharPref("network.proxy.socks", "localhost"); 317 Services.prefs.setIntPref("network.proxy.socks_port", socketserver_port); 318 Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true); 319 Services.prefs.setIntPref("network.proxy.type", 1); 320 321 var chan = makeChan(); 322 chan.asyncOpen(listener); 323 324 do_test_pending(); 325 } 326 327 function nextTest() { 328 transportAvailable = false; 329 330 if (!tests.length) { 331 do_test_finished(); 332 return; 333 } 334 335 tests.shift()(); 336 do_test_finished(); 337 } 338 339 var tests = [ 340 test_connectonly, 341 test_connectonly_noproxy, 342 test_connectonly_nonhttp, 343 ]; 344 345 function clearPrefs() { 346 Services.prefs.clearUserPref("network.proxy.ssl"); 347 Services.prefs.clearUserPref("network.proxy.ssl_port"); 348 Services.prefs.clearUserPref("network.proxy.socks"); 349 Services.prefs.clearUserPref("network.proxy.socks_port"); 350 Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost"); 351 Services.prefs.clearUserPref("network.proxy.type"); 352 } 353 354 function run_test() { 355 createProxy(); 356 357 registerCleanupFunction(clearPrefs); 358 359 nextTest(); 360 do_test_pending(); 361 }