tor-browser

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

test_IPProtectionUsage.js (7206B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { IPProtectionUsage } = ChromeUtils.importESModule(
      7  "moz-src:///browser/components/ipprotection/IPProtectionUsage.sys.mjs"
      8 );
      9 const { HttpServer } = ChromeUtils.importESModule(
     10  "resource://testing-common/httpd.sys.mjs"
     11 );
     12 const { NetUtil } = ChromeUtils.importESModule(
     13  "resource://gre/modules/NetUtil.sys.mjs"
     14 );
     15 
     16 const { XPCOMUtils } = ChromeUtils.importESModule(
     17  "resource://gre/modules/XPCOMUtils.sys.mjs"
     18 );
     19 const lazy = XPCOMUtils.declareLazy({
     20  ProxyService: {
     21    service: "@mozilla.org/network/protocol-proxy-service;1",
     22    iid: Ci.nsIProtocolProxyService,
     23  },
     24 });
     25 
     26 /**
     27 * Creates a new channel for the given URI.
     28 *
     29 * @param {*} aUri the URI to create the channel for.
     30 * @param {*} method the HTTP method to use (default: "GET").
     31 * @param {*} body the request body (for POST requests).
     32 * @param {*} proxyInfo proxy information (if any) makes this channel a proxied channel.
     33 * @returns {nsIHttpChannel | nsIProxiedChannel}
     34 */
     35 function makeChannel(aUri, method = "GET", body = null, proxyInfo = null) {
     36  let channel;
     37  if (proxyInfo) {
     38    let httpHandler = Services.io.getProtocolHandler("http");
     39    httpHandler.QueryInterface(Ci.nsIProxiedProtocolHandler);
     40    let uri = Services.io.newURI(aUri);
     41 
     42    let { loadInfo } = NetUtil.newChannel({
     43      uri,
     44      loadUsingSystemPrincipal: true,
     45    });
     46 
     47    channel = httpHandler.newProxiedChannel(
     48      uri,
     49      proxyInfo,
     50      0, // proxy resolve flags
     51      null, // proxy resolve URI
     52      loadInfo
     53    );
     54  } else {
     55    channel = NetUtil.newChannel({
     56      uri: aUri,
     57      loadUsingSystemPrincipal: true,
     58    }).QueryInterface(Ci.nsIHttpChannel);
     59    channel.requestMethod = method;
     60  }
     61 
     62  if (body) {
     63    let stream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
     64      Ci.nsIStringInputStream
     65    );
     66    stream.setUTF8Data(body);
     67    channel
     68      .QueryInterface(Ci.nsIUploadChannel)
     69      .setUploadStream(stream, "text/plain", body.length);
     70  }
     71  return channel;
     72 }
     73 
     74 function promiseChannelDone(chan) {
     75  return new Promise((resolve, reject) => {
     76    chan.asyncOpen(new ChannelListener(resolve, reject));
     77  });
     78 }
     79 
     80 /**
     81 * Mocks a channel listener.
     82 */
     83 class ChannelListener {
     84  constructor(resolve, reject) {
     85    this.resolve = resolve;
     86    this.reject = reject;
     87  }
     88  onStartRequest() {}
     89  onDataAvailable() {}
     90  onStopRequest() {
     91    this.resolve();
     92  }
     93 }
     94 
     95 /**
     96 *  • Creates a profile dir & initialises FOG.
     97 *  • Resets/flushes metrics so each test starts clean.
     98 *  • Spins‑up an HttpServer, hands its URL to the test body, then stops it.
     99 *
    100 * @param {string}   path      Path for the single route, e.g. "/get".
    101 * @param {Function} handler   httpd.js style path handler.
    102 * @param {Function} testBody  async fn(url:string):void – the real test.
    103 */
    104 async function withSetup(path, handler, testBody) {
    105  do_get_profile();
    106  Services.fog.initializeFOG();
    107 
    108  await Services.fog.testFlushAllChildren();
    109  Services.fog.testResetFOG();
    110 
    111  let server = new HttpServer();
    112  server.registerPathHandler(path, handler);
    113  server.start(-1);
    114  let port = server.identity.primaryPort;
    115  let url = `http://localhost:${port}${path}`;
    116 
    117  try {
    118    await testBody(url);
    119  } finally {
    120    await new Promise(r => server.stop(r));
    121    await Services.fog.testResetFOG();
    122  }
    123 }
    124 
    125 add_task(async function test_countChannel_get() {
    126  await withSetup(
    127    "/get",
    128    (req, resp) => {
    129      resp.setStatusLine(req.httpVersion, 200, "OK");
    130      resp.write("hello world");
    131    },
    132    async url => {
    133      let channel = makeChannel(url, "GET");
    134      await promiseChannelDone(channel);
    135 
    136      IPProtectionUsage.countChannel(channel);
    137 
    138      Assert.greater(
    139        Glean.ipprotection.usageRx.testGetValue().sum,
    140        0,
    141        "usageRx should have recorded bytes"
    142      );
    143      Assert.greater(
    144        Glean.ipprotection.usageTx.testGetValue().sum,
    145        0,
    146        "usageTx should record for GET requests"
    147      );
    148    }
    149  );
    150 });
    151 
    152 add_task(async function test_countChannel_post() {
    153  await withSetup(
    154    "/post",
    155    (req, resp) => {
    156      let body = NetUtil.readInputStreamToString(
    157        req.bodyInputStream,
    158        req.bodyInputStream.available()
    159      );
    160      Assert.equal(
    161        body,
    162        "some data",
    163        "Request body should contain 'some data'"
    164      );
    165      resp.setStatusLine(req.httpVersion, 200, "OK");
    166      resp.write("posted!");
    167    },
    168    async url => {
    169      let channel = makeChannel(url, "POST", "some data");
    170      await promiseChannelDone(channel);
    171 
    172      IPProtectionUsage.countChannel(channel);
    173 
    174      Assert.greater(
    175        Glean.ipprotection.usageRx.testGetValue().sum,
    176        0,
    177        "usageRx should have recorded bytes"
    178      );
    179      Assert.greater(
    180        Glean.ipprotection.usageTx.testGetValue().sum,
    181        0,
    182        "usageTx should record bytes for POST requests"
    183      );
    184    }
    185  );
    186 });
    187 
    188 add_task(async function test_countChannel_cache() {
    189  await withSetup(
    190    "/cache",
    191    (req, resp) => {
    192      resp.setStatusLine(req.httpVersion, 200, "OK");
    193      resp.setHeader("Cache-Control", "max-age=1000", false);
    194      resp.write("cached response");
    195    },
    196    async url => {
    197      let channel = makeChannel(url, "GET");
    198      await promiseChannelDone(channel);
    199 
    200      IPProtectionUsage.countChannel(channel);
    201 
    202      const afterRx = Glean.ipprotection.usageRx.testGetValue().sum;
    203      Assert.greater(
    204        afterRx,
    205        0,
    206        "usageRx should record bytes for first network request"
    207      );
    208 
    209      let channel2 = makeChannel(url, "GET");
    210      await promiseChannelDone(channel2);
    211 
    212      IPProtectionUsage.countChannel(channel2);
    213 
    214      Assert.equal(
    215        afterRx,
    216        Glean.ipprotection.usageRx.testGetValue().sum,
    217        "usageRx should not record bytes for cached request"
    218      );
    219    }
    220  );
    221 });
    222 
    223 add_task(async function test_shouldCountChannel() {
    224  const usage = new IPProtectionUsage();
    225  const makeInfo = key => {
    226    return lazy.ProxyService.newProxyInfo(
    227      "http",
    228      "127.0.0.1",
    229      8888,
    230      "authToken",
    231      key,
    232      1, // TRANSPARENT_PROXY_RESOLVES_HOST
    233      100,
    234      null // Failover proxy info
    235    );
    236  };
    237 
    238  const trackedIsolationKey = "is-tracked";
    239 
    240  usage.addIsolationKey(trackedIsolationKey);
    241  let testCases = [
    242    {
    243      info: makeInfo(trackedIsolationKey),
    244      result: true,
    245      description: "Tracked proxy info should be counted",
    246    },
    247    {
    248      info: undefined,
    249      result: false,
    250      description: "No proxy info should not be counted",
    251    },
    252    {
    253      info: makeInfo("is-untracked"),
    254      result: false,
    255      description: "Untracked proxy info should not be counted",
    256    },
    257    {
    258      info: makeInfo(""),
    259      result: false,
    260      description: "proxy info with empty isolation key should not be counted",
    261    },
    262  ];
    263  for (let { info, result, description } of testCases) {
    264    let channel = makeChannel("http://example.com", "GET", null, info);
    265    let shouldCount = usage.shouldCountChannel(channel);
    266    Assert.equal(
    267      shouldCount,
    268      result,
    269      `shouldCountChannel should return ${result} for ${description}`
    270    );
    271  }
    272 });