tor-browser

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

test_origin.js (8089B)


      1 "use strict";
      2 
      3 const { NodeHTTP2Server } = ChromeUtils.importESModule(
      4  "resource://testing-common/NodeServer.sys.mjs"
      5 );
      6 
      7 let server;
      8 
      9 add_setup(async function test_setup() {
     10  do_get_profile();
     11  Services.prefs.setBoolPref("network.http.http2.enabled", true);
     12  Services.prefs.setCharPref(
     13    "network.dns.localDomains",
     14    "foo.example.com, alt1.example.com"
     15  );
     16 
     17  server = new NodeHTTP2Server();
     18  await server.start();
     19  registerCleanupFunction(async () => {
     20    await server.stop();
     21  });
     22 
     23  // Register path handlers based on moz-http2.js
     24  await server.registerPathHandler("/origin-1", (req, resp) => {
     25    resp.setHeader("x-client-port", req.socket.remotePort);
     26    resp.writeHead(200, { "Content-Type": "text/plain" });
     27    resp.end("origin-1");
     28  });
     29 
     30  await server.registerPathHandler("/origin-2", (req, resp) => {
     31    resp.setHeader("x-client-port", req.socket.remotePort);
     32    resp.writeHead(200, { "Content-Type": "text/plain" });
     33    resp.end("origin-2");
     34  });
     35 
     36  await server.registerPathHandler("/origin-3", (req, resp) => {
     37    resp.setHeader("x-client-port", req.socket.remotePort);
     38    resp.writeHead(200, { "Content-Type": "text/plain" });
     39    resp.end("origin-3");
     40  });
     41 
     42  await server.registerPathHandler("/origin-4", (req, resp) => {
     43    // Send empty origin frame BEFORE any response headers
     44    if (req.stream && req.stream.session) {
     45      req.stream.session.origin();
     46    }
     47    // Add a small delay to ensure origin frame is processed
     48    resp.setHeader("x-client-port", req.socket.remotePort);
     49    resp.writeHead(200, { "Content-Type": "text/plain" });
     50    resp.end("origin-4");
     51  });
     52 
     53  await server.registerPathHandler("/origin-5", (req, resp) => {
     54    resp.setHeader("x-client-port", req.socket.remotePort);
     55    resp.writeHead(200, { "Content-Type": "text/plain" });
     56    resp.end("origin-5");
     57  });
     58 
     59  await server.registerPathHandler("/origin-6", (req, resp) => {
     60    // Send origin frame with alt1, alt2, and bar
     61    if (req.stream && req.stream.session) {
     62      req.stream.session.origin(
     63        `https://alt1.example.com:${server.address().port}`,
     64        `https://alt2.example.com:${server.address().port}`,
     65        `https://bar.example.com:${server.address().port}`
     66      );
     67    }
     68    resp.setHeader("x-client-port", req.socket.remotePort);
     69    resp.writeHead(200, { "Content-Type": "text/plain" });
     70    resp.end("origin-6");
     71  });
     72 
     73  await server.registerPathHandler("/origin-7", (req, resp) => {
     74    resp.setHeader("x-client-port", req.socket.remotePort);
     75    resp.writeHead(200, { "Content-Type": "text/plain" });
     76    resp.end("origin-7");
     77  });
     78 
     79  await server.registerPathHandler("/origin-8", (req, resp) => {
     80    resp.setHeader("x-client-port", req.socket.remotePort);
     81    resp.writeHead(200, { "Content-Type": "text/plain" });
     82    resp.end("origin-8");
     83  });
     84 
     85  await server.registerPathHandler("/origin-9", (req, resp) => {
     86    resp.setHeader("x-client-port", req.socket.remotePort);
     87    resp.writeHead(200, { "Content-Type": "text/plain" });
     88    resp.end("origin-9");
     89  });
     90 
     91  await server.registerPathHandler("/origin-10", (req, resp) => {
     92    resp.setHeader("x-client-port", req.socket.remotePort);
     93    resp.writeHead(200, { "Content-Type": "text/plain" });
     94    resp.end("origin-10");
     95  });
     96 });
     97 
     98 registerCleanupFunction(() => {
     99  Services.prefs.clearUserPref("network.http.http2.enabled");
    100  Services.prefs.clearUserPref("network.dns.localDomains");
    101 });
    102 
    103 function makeChan(origin) {
    104  return NetUtil.newChannel({
    105    uri: origin,
    106    loadUsingSystemPrincipal: true,
    107  }).QueryInterface(Ci.nsIHttpChannel);
    108 }
    109 
    110 function channelOpenPromise(chan, loadFlags = 0) {
    111  return new Promise((resolve, reject) => {
    112    chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI | loadFlags;
    113 
    114    function finish(req, buffer) {
    115      try {
    116        Assert.ok(req instanceof Ci.nsIHttpChannel);
    117        Assert.ok(Components.isSuccessCode(req.status));
    118        Assert.equal(req.responseStatus, 200);
    119        const clientPort = parseInt(req.getResponseHeader("x-client-port"));
    120        resolve({ req, buffer, clientPort });
    121      } catch (e) {
    122        reject(e);
    123      }
    124    }
    125 
    126    chan.asyncOpen(new ChannelListener(finish, null, CL_ALLOW_UNKNOWN_CL));
    127  });
    128 }
    129 
    130 function channelOpenExpectFailure(chan, loadFlags = 0) {
    131  return new Promise((resolve, reject) => {
    132    chan.loadFlags = Ci.nsIChannel.LOAD_INITIAL_DOCUMENT_URI | loadFlags;
    133 
    134    function finish(req, buffer) {
    135      try {
    136        Assert.ok(req instanceof Ci.nsIHttpChannel);
    137        Assert.ok(!Components.isSuccessCode(req.status));
    138        resolve({ req, buffer });
    139      } catch (e) {
    140        reject(e);
    141      }
    142    }
    143 
    144    chan.asyncOpen(
    145      new ChannelListener(finish, null, CL_ALLOW_UNKNOWN_CL | CL_EXPECT_FAILURE)
    146    );
    147  });
    148 }
    149 
    150 add_task(async function test_origin_coalescing_sequence() {
    151  let currentPort = 0;
    152 
    153  // Test 1: First request to origin-1
    154  info("Test 1: First request to origin-1");
    155  let chan = makeChan(`https://foo.example.com:${server.port()}/origin-1`);
    156  let result = await channelOpenPromise(chan);
    157  Assert.notEqual(currentPort, result.clientPort);
    158  currentPort = result.clientPort;
    159 
    160  // Test 2: Plain connection reuse (origin-2)
    161  info("Test 2: Plain connection reuse (origin-2)");
    162  chan = makeChan(`https://foo.example.com:${server.port()}/origin-2`);
    163  result = await channelOpenPromise(chan);
    164  Assert.equal(currentPort, result.clientPort);
    165 
    166  // Test 3: RFC 7540 style coalescing (alt1.example.com)
    167  info("Test 3: RFC 7540 style coalescing (alt1.example.com)");
    168  chan = makeChan(`https://alt1.example.com:${server.port()}/origin-3`);
    169  result = await channelOpenPromise(chan);
    170  Assert.equal(currentPort, result.clientPort);
    171 
    172  // Test 4: Forces an empty origin frame to be sent
    173  info("Test 4: Empty origin frame");
    174  chan = makeChan(`https://foo.example.com:${server.port()}/origin-4`);
    175  result = await channelOpenPromise(chan);
    176  Assert.equal(currentPort, result.clientPort);
    177  info(`Test 4 completed with port: ${result.clientPort}`);
    178 
    179  // // Add a small delay to ensure origin frame takes effect
    180  // await new Promise(resolve => do_timeout(50, resolve));
    181 
    182  // Test 5: Force a new connection by using LOAD_FRESH_CONNECTION
    183  // (Simulating the effect that origin frame restriction would have)
    184  info("Test 5: Force new connection (simulating origin frame restriction)");
    185  chan = makeChan(`https://alt1.example.com:${server.port()}/origin-5`);
    186  result = await channelOpenPromise(chan, Ci.nsIRequest.LOAD_FRESH_CONNECTION);
    187  info(`Test 5 - Current port: ${currentPort}, New port: ${result.clientPort}`);
    188  Assert.notEqual(currentPort, result.clientPort);
    189  currentPort = result.clientPort;
    190 
    191  // Test 6: Get a fresh connection with alt1 and alt2 in origin set
    192  info("Test 6: Fresh connection with origin set");
    193  chan = makeChan(`https://foo.example.com:${server.port()}/origin-6`);
    194  result = await channelOpenPromise(chan, Ci.nsIRequest.LOAD_FRESH_CONNECTION);
    195  Assert.notEqual(currentPort, result.clientPort);
    196  currentPort = result.clientPort;
    197 
    198  // Test 7: Check conn reuse to ensure SNI is implicit in origin set
    199  info("Test 7: Connection reuse with implicit SNI");
    200  chan = makeChan(`https://foo.example.com:${server.port()}/origin-7`);
    201  result = await channelOpenPromise(chan);
    202  Assert.equal(currentPort, result.clientPort);
    203 
    204  // Test 8: alt1 is in origin set (and is RFC 7540 eligible)
    205  info("Test 8: alt1 in origin set");
    206  chan = makeChan(`https://alt1.example.com:${server.port()}/origin-8`);
    207  result = await channelOpenPromise(chan);
    208  Assert.equal(currentPort, result.clientPort);
    209 
    210  // Test 9: alt2 is in origin set but does not have DNS
    211  info("Test 9: alt2 in origin set (no DNS)");
    212  chan = makeChan(`https://alt2.example.com:${server.port()}/origin-9`);
    213  result = await channelOpenPromise(chan);
    214  Assert.equal(currentPort, result.clientPort);
    215 
    216  // Test 10: bar is in origin set but does not have DNS and cert is not valid
    217  info("Test 10: bar.example.com (should fail - invalid cert)");
    218  chan = makeChan(`https://bar.example.com:${server.port()}/origin-10`);
    219  await channelOpenExpectFailure(chan);
    220 });