tor-browser

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

browser_test_local_network_trackers.js (8650B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 https://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 const { UrlClassifierTestUtils } = ChromeUtils.importESModule(
      7  "resource://testing-common/UrlClassifierTestUtils.sys.mjs"
      8 );
      9 
     10 const { HttpServer } = ChromeUtils.importESModule(
     11  "resource://testing-common/httpd.sys.mjs"
     12 );
     13 
     14 const baseURL = getRootDirectory(gTestPath).replace(
     15  "chrome://mochitests/content",
     16  "https://example.com"
     17 );
     18 
     19 const { PermissionTestUtils } = ChromeUtils.importESModule(
     20  "resource://testing-common/PermissionTestUtils.sys.mjs"
     21 );
     22 
     23 const { RemoteSettings } = ChromeUtils.importESModule(
     24  "resource://services-settings/remote-settings.sys.mjs"
     25 );
     26 
     27 const COLLECTION_NAME = "remote-permissions";
     28 let rs = RemoteSettings(COLLECTION_NAME);
     29 
     30 async function remoteSettingsSync({ created, updated, deleted }) {
     31  await rs.emit("sync", {
     32    data: {
     33      created,
     34      updated,
     35      deleted,
     36    },
     37  });
     38 }
     39 
     40 async function restorePermissions() {
     41  info("Restoring permissions");
     42  Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk");
     43  Services.perms.removeAll();
     44 }
     45 
     46 add_setup(async function () {
     47  await SpecialPowers.pushPrefEnv({
     48    set: [
     49      ["permissions.manager.defaultsUrl", ""],
     50      ["network.websocket.delay-failed-reconnects", false],
     51      ["network.websocket.max-connections", 1000],
     52      ["network.lna.block_trackers", true],
     53      ["network.lna.address_space.public.override", "127.0.0.1:4443"],
     54      ["network.lna.blocking", true],
     55      ["network.lna.websocket.enabled", true],
     56      // always select allow actions for user prompts
     57      ["network.localhost.prompt.testing", true],
     58      ["network.localnetwork.prompt.testing", true],
     59      ["network.localhost.prompt.testing.allow", true],
     60      ["network.localnetwork.prompt.testing.allow", true],
     61    ],
     62  });
     63 
     64  // Make sure we start off "empty". Any RemoteSettings values must be
     65  // purged now to comply with test expectations.
     66  Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk");
     67  registerCleanupFunction(restorePermissions);
     68 });
     69 
     70 requestLongerTimeout(10);
     71 
     72 function observeAndCheck(testType, rand, expectedStatus, message) {
     73  return new Promise(resolve => {
     74    let observer = {
     75      observe(subject, topic) {
     76        if (topic !== "http-on-stop-request") {
     77          return;
     78        }
     79 
     80        let url = `http://localhost:21555/?type=${testType}&rand=${rand}`;
     81        let channel = subject.QueryInterface(Ci.nsIHttpChannel);
     82        if (!channel || channel.URI.spec !== url) {
     83          return;
     84        }
     85 
     86        is(channel.status, expectedStatus, message);
     87 
     88        Services.obs.removeObserver(observer, "http-on-stop-request");
     89        resolve();
     90      },
     91    };
     92    Services.obs.addObserver(observer, "http-on-stop-request");
     93  });
     94 }
     95 
     96 let testCases = [
     97  {
     98    type: "fetch",
     99    nonTrackerStatus: Cr.NS_OK,
    100    trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    101  },
    102  {
    103    type: "xhr",
    104    nonTrackerStatus: Cr.NS_OK,
    105    trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    106  },
    107  {
    108    type: "img",
    109    nonTrackerStatus: Cr.NS_OK,
    110    trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    111  },
    112  {
    113    type: "video",
    114    nonTrackerStatus: Cr.NS_OK,
    115    trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    116  },
    117  {
    118    type: "audio",
    119    nonTrackerStatus: Cr.NS_OK,
    120    trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    121  },
    122  {
    123    type: "iframe",
    124    nonTrackerStatus: Cr.NS_OK,
    125    trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    126  },
    127  {
    128    type: "script",
    129    nonTrackerStatus: Cr.NS_OK,
    130    trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    131  },
    132  { type: "font", nonTrackerStatus: Cr.NS_OK, trackerStatus: Cr.NS_OK }, // TODO
    133  {
    134    type: "websocket",
    135    nonTrackerStatus: Cr.NS_ERROR_WEBSOCKET_CONNECTION_REFUSED,
    136    trackerStatus: Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED,
    137  },
    138 ];
    139 
    140 // Tests that a public->private fetch load initiated from
    141 // a tracking script will fail.
    142 add_task(async function test_tracker_initiated_lna_fetch() {
    143  let server = new HttpServer();
    144  server.start(21555);
    145  registerCleanupFunction(async () => {
    146    await server.stop();
    147  });
    148  server.registerPathHandler("/", (request, response) => {
    149    const params = new URLSearchParams(request.queryString);
    150    const type = params.get("type");
    151 
    152    response.setHeader("Access-Control-Allow-Origin", "*", false);
    153 
    154    switch (type) {
    155      case "img":
    156        response.setHeader("Content-Type", "image/gif", false);
    157        response.setStatusLine(request.httpVersion, 200, "OK");
    158        // 1x1 transparent GIF
    159        response.write(
    160          atob("R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==")
    161        );
    162        return;
    163 
    164      case "audio":
    165        response.setHeader("Content-Type", "audio/wav", false);
    166        response.setStatusLine(request.httpVersion, 200, "OK");
    167        // Silent WAV (44-byte header + no data)
    168        response.write(
    169          atob("UklGRhYAAABXQVZFZm10IBAAAAABAAEAIlYAAESsAAACABAAZGF0YQAAAAA=")
    170        );
    171        return;
    172 
    173      case "video":
    174        response.setHeader("Content-Type", "video/mp4", false);
    175        response.setStatusLine(request.httpVersion, 200, "OK");
    176        // Minimal MP4 file header; may not render but satisfies loader
    177        response.write(
    178          atob(
    179            "GkXfo0AgQoaBAUL3gQFC8oEEQvOBCEKCQAR3ZWJtQoeBAkKFgQIYU4BnQI0VSalmQCgq17FAAw9CQE2AQAZ3aGFtbXlXQUAGd2hhbW15RIlACECPQAAAAAAAFlSua0AxrkAu14EBY8WBAZyBACK1nEADdW5khkAFVl9WUDglhohAA1ZQOIOBAeBABrCBCLqBCB9DtnVAIueBAKNAHIEAAIAwAQCdASoIAAgAAUAmJaQAA3AA/vz0AAA="
    180          )
    181        );
    182        return;
    183 
    184      default:
    185        response.setHeader("Content-Type", "text/plain", false);
    186        response.setStatusLine(request.httpVersion, 200, "OK");
    187        response.write("hello");
    188    }
    189  });
    190 
    191  for (let test of testCases) {
    192    let rand = Math.random();
    193    let promise = observeAndCheck(
    194      test.type,
    195      rand,
    196      test.nonTrackerStatus,
    197      `expected correct status for non-tracker ${test.type} test`
    198    );
    199    let tab = await BrowserTestUtils.openNewForegroundTab(
    200      gBrowser,
    201      baseURL + `page_with_trackers.html?test=${test.type}&rand=${rand}`
    202    );
    203 
    204    await promise;
    205    gBrowser.removeTab(tab);
    206  }
    207 
    208  registerCleanupFunction(UrlClassifierTestUtils.cleanupTestTrackers);
    209  await UrlClassifierTestUtils.addTestTrackers();
    210 
    211  for (let test of testCases) {
    212    Services.fog.testResetFOG();
    213    let rand = Math.random();
    214    let promise = observeAndCheck(
    215      test.type,
    216      rand,
    217      test.trackerStatus,
    218      `expected correct status for tracker ${test.type} test`
    219    );
    220    let tab = await BrowserTestUtils.openNewForegroundTab(
    221      gBrowser,
    222      baseURL + `page_with_trackers.html?test=${test.type}&rand=${rand}`
    223    );
    224 
    225    await promise;
    226    gBrowser.removeTab(tab);
    227    is(
    228      await Glean.networking.localNetworkBlockedTracker.testGetValue(),
    229      test.trackerStatus == Cr.NS_ERROR_LOCAL_NETWORK_ACCESS_DENIED ? 1 : null
    230    );
    231  }
    232 
    233  // check that when adding the permission the fetch req succeeds.
    234  PermissionTestUtils.add(
    235    baseURL + "page_with_trackers.html",
    236    "localhost",
    237    Services.perms.ALLOW_ACTION,
    238    Services.perms.EXPIRE_NEVER
    239  );
    240 
    241  for (let test of testCases) {
    242    let rand = Math.random();
    243    let promise = observeAndCheck(
    244      test.type,
    245      rand,
    246      test.nonTrackerStatus,
    247      `expected correct status for tracker ${test.type} test with permission`
    248    );
    249    let tab = await BrowserTestUtils.openNewForegroundTab(
    250      gBrowser,
    251      baseURL + `page_with_trackers.html?test=${test.type}&rand=${rand}`
    252    );
    253 
    254    await promise;
    255    gBrowser.removeTab(tab);
    256  }
    257 
    258  PermissionTestUtils.remove(baseURL + "page_with_trackers.html", "localhost");
    259 
    260  // This time check that the remote permission service can automatically set up the permission for this domain.
    261  const ORIGIN_1 = "https://example.com";
    262  const TEST_PERMISSION_1 = "localhost";
    263  await remoteSettingsSync({
    264    created: [
    265      {
    266        origin: ORIGIN_1,
    267        type: TEST_PERMISSION_1,
    268        capability: Ci.nsIPermissionManager.ALLOW_ACTION,
    269      },
    270    ],
    271  });
    272 
    273  for (let test of testCases) {
    274    let rand = Math.random();
    275    let promise = observeAndCheck(
    276      test.type,
    277      rand,
    278      test.nonTrackerStatus,
    279      `expected correct status for tracker ${test.type} test with permission from remote-settings`
    280    );
    281    let tab = await BrowserTestUtils.openNewForegroundTab(
    282      gBrowser,
    283      baseURL + `page_with_trackers.html?test=${test.type}&rand=${rand}`
    284    );
    285 
    286    await promise;
    287    gBrowser.removeTab(tab);
    288  }
    289 
    290  restorePermissions();
    291 });