tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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 }