tor-browser

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

test_proxy_cancel.js (13230B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 /* globals setTimeout */
      8 
      9 // We don't normally allow localhost channels to be proxied, but this
     10 // is easier than updating all the certs and/or domains.
     11 Services.prefs.setBoolPref("network.proxy.allow_hijacking_localhost", true);
     12 registerCleanupFunction(() => {
     13  Services.prefs.clearUserPref("network.proxy.allow_hijacking_localhost");
     14 });
     15 
     16 const { TestUtils } = ChromeUtils.importESModule(
     17  "resource://testing-common/TestUtils.sys.mjs"
     18 );
     19 
     20 const {
     21  NodeHTTPServer,
     22  NodeHTTPSServer,
     23  NodeHTTP2Server,
     24  NodeHTTPProxyServer,
     25  NodeHTTPSProxyServer,
     26  NodeHTTP2ProxyServer,
     27  with_node_servers,
     28 } = ChromeUtils.importESModule("resource://testing-common/NodeServer.sys.mjs");
     29 
     30 function makeChan(uri) {
     31  let chan = NetUtil.newChannel({
     32    uri,
     33    loadUsingSystemPrincipal: true,
     34  }).QueryInterface(Ci.nsIHttpChannel);
     35  chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI;
     36  return chan;
     37 }
     38 
     39 add_setup(async function setup() {
     40  // See Bug 1878505
     41  Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
     42  registerCleanupFunction(async () => {
     43    Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
     44  });
     45 });
     46 
     47 add_task(async function test_cancel_after_asyncOpen() {
     48  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
     49    Ci.nsIX509CertDB
     50  );
     51  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
     52  addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
     53 
     54  let proxies = [
     55    NodeHTTPProxyServer,
     56    NodeHTTPSProxyServer,
     57    NodeHTTP2ProxyServer,
     58  ];
     59  for (let p of proxies) {
     60    let proxy = new p();
     61    await proxy.start();
     62    registerCleanupFunction(async () => {
     63      await proxy.stop();
     64    });
     65    await with_node_servers(
     66      [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
     67      async server => {
     68        info(`Testing ${p.name} with ${server.constructor.name}`);
     69        await server.execute(
     70          `global.server_name = "${server.constructor.name}";`
     71        );
     72 
     73        await server.registerPathHandler("/test", (req, resp) => {
     74          resp.writeHead(200);
     75          resp.end(global.server_name);
     76        });
     77 
     78        let chan = makeChan(`${server.origin()}/test`);
     79        let openPromise = new Promise(resolve => {
     80          chan.asyncOpen(
     81            new ChannelListener(
     82              (req, buff) => resolve({ req, buff }),
     83              null,
     84              CL_EXPECT_FAILURE
     85            )
     86          );
     87        });
     88        chan.cancel(Cr.NS_ERROR_ABORT);
     89        let { req } = await openPromise;
     90        Assert.equal(req.status, Cr.NS_ERROR_ABORT);
     91      }
     92    );
     93    await proxy.stop();
     94  }
     95 });
     96 
     97 // const NS_NET_STATUS_CONNECTING_TO = 0x4b0007;
     98 // const NS_NET_STATUS_CONNECTED_TO = 0x4b0004;
     99 // const NS_NET_STATUS_SENDING_TO = 0x4b0005;
    100 const NS_NET_STATUS_WAITING_FOR = 0x4b000a; // 2152398858
    101 const NS_NET_STATUS_RECEIVING_FROM = 0x4b0006;
    102 // const NS_NET_STATUS_TLS_HANDSHAKE_STARTING = 0x4b000c; // 2152398860
    103 // const NS_NET_STATUS_TLS_HANDSHAKE_ENDED = 0x4b000d; // 2152398861
    104 
    105 add_task(async function test_cancel_after_connect_http2proxy() {
    106  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    107    Ci.nsIX509CertDB
    108  );
    109  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
    110  addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
    111 
    112  await with_node_servers(
    113    [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
    114    async server => {
    115      // Set up a proxy for each server to make sure proxy state is clean
    116      // for each test.
    117      let proxy = new NodeHTTP2ProxyServer();
    118      await proxy.start();
    119      registerCleanupFunction(async () => {
    120        await proxy.stop();
    121      });
    122      await proxy.execute(`
    123        global.session_counter = 0;
    124        global.proxy.on("session", () => {
    125          global.session_counter++;
    126        });
    127      `);
    128 
    129      info(`Testing ${proxy.constructor.name} with ${server.constructor.name}`);
    130      await server.execute(
    131        `global.server_name = "${server.constructor.name}";`
    132      );
    133 
    134      await server.registerPathHandler("/test", (req, resp) => {
    135        global.reqCount = (global.reqCount || 0) + 1;
    136        resp.writeHead(200);
    137        resp.end(global.server_name);
    138      });
    139 
    140      let chan = makeChan(`${server.origin()}/test`);
    141      chan.notificationCallbacks = {
    142        QueryInterface: ChromeUtils.generateQI([
    143          "nsIInterfaceRequestor",
    144          "nsIProgressEventSink",
    145        ]),
    146 
    147        getInterface(iid) {
    148          return this.QueryInterface(iid);
    149        },
    150 
    151        onProgress() {},
    152        onStatus(request, status) {
    153          info(`status = ${status}`);
    154          // XXX(valentin): Is this the best status to be cancelling?
    155          if (status == NS_NET_STATUS_WAITING_FOR) {
    156            info("cancelling connected channel");
    157            chan.cancel(Cr.NS_ERROR_ABORT);
    158          }
    159        },
    160      };
    161      let openPromise = new Promise(resolve => {
    162        chan.asyncOpen(
    163          new ChannelListener(
    164            (req, buff) => resolve({ req, buff }),
    165            null,
    166            CL_EXPECT_FAILURE
    167          )
    168        );
    169      });
    170      let { req } = await openPromise;
    171      Assert.equal(req.status, Cr.NS_ERROR_ABORT);
    172 
    173      // Since we're cancelling just after connect, we'd expect that no
    174      // requests are actually registered. But because we're cancelling on the
    175      // main thread, and the request is being performed on the socket thread,
    176      // it might actually reach the server, especially in chaos test mode.
    177      // Assert.equal(
    178      //   await server.execute(`global.reqCount || 0`),
    179      //   0,
    180      //   `No requests should have been made at this point`
    181      // );
    182      Assert.equal(await proxy.execute(`global.session_counter`), 1);
    183 
    184      chan = makeChan(`${server.origin()}/test`);
    185      await new Promise(resolve => {
    186        chan.asyncOpen(
    187          new ChannelListener(
    188            // eslint-disable-next-line no-shadow
    189            (req, buff) => resolve({ req, buff }),
    190            null,
    191            CL_ALLOW_UNKNOWN_CL
    192          )
    193        );
    194      });
    195 
    196      // Check that there's still only one session.
    197      Assert.equal(await proxy.execute(`global.session_counter`), 1);
    198      await proxy.stop();
    199    }
    200  );
    201 });
    202 
    203 add_task(async function test_cancel_after_sending_request() {
    204  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    205    Ci.nsIX509CertDB
    206  );
    207  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
    208  addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
    209 
    210  await with_node_servers(
    211    [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
    212    async server => {
    213      let proxies = [
    214        NodeHTTPProxyServer,
    215        NodeHTTPSProxyServer,
    216        NodeHTTP2ProxyServer,
    217      ];
    218      for (let p of proxies) {
    219        let proxy = new p();
    220        await proxy.start();
    221        registerCleanupFunction(async () => {
    222          await proxy.stop();
    223        });
    224 
    225        await proxy.execute(`
    226        global.session_counter = 0;
    227        global.proxy.on("session", () => {
    228          global.session_counter++;
    229        });
    230      `);
    231        info(`Testing ${p.name} with ${server.constructor.name}`);
    232        await server.execute(
    233          `global.server_name = "${server.constructor.name}";`
    234        );
    235 
    236        await server.registerPathHandler("/test", (req, resp) => {
    237          // Here we simmulate a slow response to give the test time to
    238          // cancel the channel before receiving the response.
    239          global.request_count = (global.request_count || 0) + 1;
    240          // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    241          setTimeout(() => {
    242            resp.writeHead(200);
    243            resp.end(global.server_name);
    244          }, 2000);
    245        });
    246        await server.registerPathHandler("/instant", (req, resp) => {
    247          resp.writeHead(200);
    248          resp.end(global.server_name);
    249        });
    250 
    251        // It seems proxy.on("session") only gets emitted after a full request.
    252        // So we first load a simple request, then the long lasting request
    253        // that we then cancel before it has the chance to complete.
    254        let chan = makeChan(`${server.origin()}/instant`);
    255        await new Promise(resolve => {
    256          chan.asyncOpen(
    257            new ChannelListener(resolve, null, CL_ALLOW_UNKNOWN_CL)
    258          );
    259        });
    260 
    261        chan = makeChan(`${server.origin()}/test`);
    262        let openPromise = new Promise(resolve => {
    263          chan.asyncOpen(
    264            new ChannelListener(
    265              (req, buff) => resolve({ req, buff }),
    266              null,
    267              CL_EXPECT_FAILURE
    268            )
    269          );
    270        });
    271        // XXX(valentin) This might be a little racy
    272        await TestUtils.waitForCondition(async () => {
    273          return (await server.execute("global.request_count")) > 0;
    274        });
    275 
    276        chan.cancel(Cr.NS_ERROR_ABORT);
    277 
    278        let { req } = await openPromise;
    279        Assert.equal(req.status, Cr.NS_ERROR_ABORT);
    280 
    281        async function checkSessionCounter() {
    282          if (p.name == "NodeHTTP2ProxyServer") {
    283            Assert.equal(await proxy.execute(`global.session_counter`), 1);
    284          }
    285        }
    286 
    287        await checkSessionCounter();
    288 
    289        chan = makeChan(`${server.origin()}/instant`);
    290        await new Promise(resolve => {
    291          chan.asyncOpen(
    292            new ChannelListener(
    293              // eslint-disable-next-line no-shadow
    294              (req, buff) => resolve({ req, buff }),
    295              null,
    296              CL_ALLOW_UNKNOWN_CL
    297            )
    298          );
    299        });
    300        await checkSessionCounter();
    301 
    302        await proxy.stop();
    303      }
    304    }
    305  );
    306 });
    307 
    308 add_task(async function test_cancel_during_response() {
    309  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
    310    Ci.nsIX509CertDB
    311  );
    312  addCertFromFile(certdb, "http2-ca.pem", "CTu,u,u");
    313  addCertFromFile(certdb, "proxy-ca.pem", "CTu,u,u");
    314 
    315  await with_node_servers(
    316    [NodeHTTPServer, NodeHTTPSServer, NodeHTTP2Server],
    317    async server => {
    318      let proxies = [
    319        NodeHTTPProxyServer,
    320        NodeHTTPSProxyServer,
    321        NodeHTTP2ProxyServer,
    322      ];
    323      for (let p of proxies) {
    324        let proxy = new p();
    325        await proxy.start();
    326        registerCleanupFunction(async () => {
    327          await proxy.stop();
    328        });
    329 
    330        await proxy.execute(`
    331        global.session_counter = 0;
    332        global.proxy.on("session", () => {
    333          global.session_counter++;
    334        });
    335      `);
    336 
    337        info(`Testing ${p.name} with ${server.constructor.name}`);
    338        await server.execute(
    339          `global.server_name = "${server.constructor.name}";`
    340        );
    341 
    342        await server.registerPathHandler("/test", (req, resp) => {
    343          resp.writeHead(200);
    344          resp.write("a".repeat(1000));
    345          // Here we send the response back in two chunks.
    346          // The channel should be cancelled after the first one.
    347          // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    348          setTimeout(() => {
    349            resp.write("a".repeat(1000));
    350            resp.end(global.server_name);
    351          }, 2000);
    352        });
    353        await server.registerPathHandler("/instant", (req, resp) => {
    354          resp.writeHead(200);
    355          resp.end(global.server_name);
    356        });
    357 
    358        let chan = makeChan(`${server.origin()}/test`);
    359 
    360        chan.notificationCallbacks = {
    361          QueryInterface: ChromeUtils.generateQI([
    362            "nsIInterfaceRequestor",
    363            "nsIProgressEventSink",
    364          ]),
    365 
    366          getInterface(iid) {
    367            return this.QueryInterface(iid);
    368          },
    369 
    370          onProgress(request, progress, progressMax) {
    371            info(`progress: ${progress}/${progressMax}`);
    372            // Check that we never get more than 1000 bytes.
    373            Assert.equal(progress, 1000);
    374          },
    375          onStatus(request, status) {
    376            if (status == NS_NET_STATUS_RECEIVING_FROM) {
    377              info("cancelling when receiving request");
    378              chan.cancel(Cr.NS_ERROR_ABORT);
    379            }
    380          },
    381        };
    382 
    383        let openPromise = new Promise(resolve => {
    384          chan.asyncOpen(
    385            new ChannelListener(
    386              (req, buff) => resolve({ req, buff }),
    387              null,
    388              CL_EXPECT_FAILURE
    389            )
    390          );
    391        });
    392 
    393        let { req } = await openPromise;
    394        Assert.equal(req.status, Cr.NS_ERROR_ABORT);
    395 
    396        async function checkSessionCounter() {
    397          if (p.name == "NodeHTTP2ProxyServer") {
    398            Assert.equal(await proxy.execute(`global.session_counter`), 1);
    399          }
    400        }
    401 
    402        await checkSessionCounter();
    403 
    404        chan = makeChan(`${server.origin()}/instant`);
    405        await new Promise(resolve => {
    406          chan.asyncOpen(
    407            new ChannelListener(
    408              // eslint-disable-next-line no-shadow
    409              (req, buff) => resolve({ req, buff }),
    410              null,
    411              CL_ALLOW_UNKNOWN_CL
    412            )
    413          );
    414        });
    415 
    416        await checkSessionCounter();
    417 
    418        await proxy.stop();
    419      }
    420    }
    421  );
    422 });