tor-browser

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

browser_networkobserver_override.js (10786B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 requestLongerTimeout(3);
      6 const TEST_URL = URL_ROOT + "doc_network-observer.html";
      7 const TEST_URL_CSP = URL_ROOT + "override_script_src_self.html";
      8 const REQUEST_URL =
      9  URL_ROOT + `sjs_network-observer-test-server.sjs?sts=200&fmt=js`;
     10 const CORS_REQUEST_URL = REQUEST_URL.replace("example.com", "plop.example.com");
     11 const CSP_SCRIPT_TO_OVERRIDE = URL_ROOT + "csp_script_to_override.js";
     12 const GZIPPED_REQUEST_URL = URL_ROOT + `gzipped.sjs`;
     13 const OVERRIDE_FILENAME = "override.js";
     14 const OVERRIDE_HTML_FILENAME = "override.html";
     15 
     16 add_task(async function testLocalOverride() {
     17  await addTab(TEST_URL);
     18 
     19  let eventsCount = 0;
     20  const networkObserver = new NetworkObserver({
     21    ignoreChannelFunction: channel =>
     22      ![REQUEST_URL, CORS_REQUEST_URL].includes(channel.URI.spec),
     23    onNetworkEvent: event => {
     24      info("received a network event");
     25      eventsCount++;
     26      return createNetworkEventOwner(event);
     27    },
     28  });
     29 
     30  const overrideFile = getChromeDir(getResolvedURI(gTestPath));
     31  overrideFile.append(OVERRIDE_FILENAME);
     32  info(" override " + REQUEST_URL + " to " + overrideFile.path + "\n");
     33  networkObserver.override(REQUEST_URL, overrideFile.path);
     34 
     35  info("Assert that request and cached request are overriden");
     36  await SpecialPowers.spawn(
     37    gBrowser.selectedBrowser,
     38    [REQUEST_URL],
     39    async _url => {
     40      const response = await content.wrappedJSObject.fetch(_url);
     41      const responsecontent = await response.text();
     42      is(
     43        responsecontent,
     44        `"use strict";\ndocument.title = "Override script loaded";\n`,
     45        "the response content has been overriden"
     46      );
     47      const secondResponse = await content.wrappedJSObject.fetch(_url);
     48      const secondResponsecontent = await secondResponse.text();
     49      is(
     50        secondResponsecontent,
     51        `"use strict";\ndocument.title = "Override script loaded";\n`,
     52        "the cached response content has been overriden"
     53      );
     54    }
     55  );
     56 
     57  info("Assert that JS scripts can be overriden");
     58  await SpecialPowers.spawn(
     59    gBrowser.selectedBrowser,
     60    [REQUEST_URL],
     61    async _url => {
     62      const script = content.document.createElement("script");
     63      const onLoad = new Promise(resolve =>
     64        script.addEventListener("load", resolve, { once: true })
     65      );
     66      script.src = _url;
     67      content.document.body.appendChild(script);
     68      await onLoad;
     69      is(
     70        content.document.title,
     71        "Override script loaded",
     72        "The <script> tag content has been overriden and correctly evaluated"
     73      );
     74    }
     75  );
     76 
     77  info(`Assert that JS scripts with crossorigin="anonymous" can be overriden`);
     78  networkObserver.override(CORS_REQUEST_URL, overrideFile.path);
     79 
     80  await SpecialPowers.spawn(
     81    gBrowser.selectedBrowser,
     82    [CORS_REQUEST_URL],
     83    async _url => {
     84      content.document.title = "title before crossorigin=anonymous evaluation";
     85      const script = content.document.createElement("script");
     86      script.setAttribute("crossorigin", "anonymous");
     87      script.crossOrigin = "anonymous";
     88      const onLoad = new Promise(resolve =>
     89        script.addEventListener("load", resolve, { once: true })
     90      );
     91      script.src = _url;
     92      content.document.body.appendChild(script);
     93      await onLoad;
     94      is(
     95        content.document.title,
     96        "Override script loaded",
     97        `The <script crossorigin="anonymous"> tag content has been overriden and correctly evaluated`
     98      );
     99    }
    100  );
    101 
    102  await BrowserTestUtils.waitForCondition(() => eventsCount >= 1);
    103 
    104  info(
    105    `Assert that requests which would require a CORS preflight can be overridden`
    106  );
    107  await SpecialPowers.spawn(
    108    gBrowser.selectedBrowser,
    109    [CORS_REQUEST_URL],
    110    async _url => {
    111      // NOTE: This is intentionally using `content.fetch` and not `content.wrappedJSObject.fetch`,
    112      // otherwise the `LoadRequireCORSPreflight` flag is not set for the request.
    113      const response = await content.fetch(_url, {
    114        // Use a extra header to force a CORS preflight.
    115        headers: { "X-PINGOTHER": "pingpong" },
    116      });
    117      const responsecontent = await response.text();
    118      is(response.status, 200);
    119      is(
    120        responsecontent,
    121        `"use strict";\ndocument.title = "Override script loaded";\n`,
    122        "the content for the CORS (with preflight) request has been overriden"
    123      );
    124    }
    125  );
    126 
    127  await BrowserTestUtils.waitForCondition(() => eventsCount >= 2);
    128  networkObserver.destroy();
    129 });
    130 
    131 add_task(async function testHtmlFileOverride() {
    132  let eventsCount = 0;
    133  const networkObserver = new NetworkObserver({
    134    ignoreChannelFunction: channel => channel.URI.spec !== TEST_URL,
    135    onNetworkEvent: event => {
    136      info("received a network event");
    137      eventsCount++;
    138      return createNetworkEventOwner(event);
    139    },
    140  });
    141 
    142  const overrideFile = getChromeDir(getResolvedURI(gTestPath));
    143  overrideFile.append(OVERRIDE_HTML_FILENAME);
    144  info(" override " + TEST_URL + " to " + overrideFile.path + "\n");
    145  networkObserver.override(TEST_URL, overrideFile.path);
    146 
    147  await addTab(TEST_URL);
    148  await SpecialPowers.spawn(
    149    gBrowser.selectedBrowser,
    150    [TEST_URL],
    151    async pageUrl => {
    152      is(
    153        content.document.documentElement.outerHTML,
    154        "<html><head></head><body>Overriden!\n</body></html>",
    155        "The content of the HTML has been overriden"
    156      );
    157      is(
    158        content.location.href,
    159        pageUrl,
    160        "The location of the page is still the original one"
    161      );
    162    }
    163  );
    164  await BrowserTestUtils.waitForCondition(() => eventsCount >= 1);
    165  networkObserver.destroy();
    166 });
    167 
    168 // Exact same test, but with a gzipped request, which requires very special treatment
    169 add_task(async function testLocalOverrideGzipped() {
    170  await addTab(TEST_URL);
    171 
    172  let eventsCount = 0;
    173  const networkObserver = new NetworkObserver({
    174    ignoreChannelFunction: channel => channel.URI.spec !== GZIPPED_REQUEST_URL,
    175    onNetworkEvent: event => {
    176      info("received a network event");
    177      eventsCount++;
    178      return createNetworkEventOwner(event);
    179    },
    180  });
    181 
    182  const overrideFile = getChromeDir(getResolvedURI(gTestPath));
    183  overrideFile.append(OVERRIDE_FILENAME);
    184  info(" override " + GZIPPED_REQUEST_URL + " to " + overrideFile.path + "\n");
    185  networkObserver.override(GZIPPED_REQUEST_URL, overrideFile.path);
    186 
    187  await SpecialPowers.spawn(
    188    gBrowser.selectedBrowser,
    189    [GZIPPED_REQUEST_URL],
    190    async _url => {
    191      const response = await content.wrappedJSObject.fetch(_url);
    192      const responsecontent = await response.text();
    193      is(
    194        responsecontent,
    195        `"use strict";\ndocument.title = "Override script loaded";\n`,
    196        "the response content for the gzipped script has been overriden"
    197      );
    198      const secondResponse = await content.wrappedJSObject.fetch(_url);
    199      const secondResponsecontent = await secondResponse.text();
    200      is(
    201        secondResponsecontent,
    202        `"use strict";\ndocument.title = "Override script loaded";\n`,
    203        "the cached response content for the gzipped script has been overriden"
    204      );
    205    }
    206  );
    207 
    208  await SpecialPowers.spawn(
    209    gBrowser.selectedBrowser,
    210    [GZIPPED_REQUEST_URL],
    211    async _url => {
    212      const script = content.document.createElement("script");
    213      const onLoad = new Promise(resolve =>
    214        script.addEventListener("load", resolve, { once: true })
    215      );
    216      script.src = _url;
    217      content.document.body.appendChild(script);
    218      await onLoad;
    219      is(
    220        content.document.title,
    221        "Override script loaded",
    222        "The <script> tag content for the gzipped script has been overriden and correctly evaluated"
    223      );
    224    }
    225  );
    226 
    227  await BrowserTestUtils.waitForCondition(() => eventsCount >= 1);
    228 
    229  networkObserver.destroy();
    230 });
    231 
    232 // Check that the override works even if the page uses script 'self' as CSP.
    233 add_task(async function testLocalOverrideCSP() {
    234  await addTab(TEST_URL_CSP);
    235 
    236  const url = CSP_SCRIPT_TO_OVERRIDE;
    237  const browser = gBrowser.selectedBrowser;
    238  const originalText = await getResponseText(url, browser);
    239  is(
    240    originalText,
    241    `"use strict";\ndocument.title = "CSP script to override loaded";\n`,
    242    "the response content for the CSP script is the original one"
    243  );
    244 
    245  let eventsCount = 0;
    246  const networkObserver = new NetworkObserver({
    247    ignoreChannelFunction: channel => channel.URI.spec !== url,
    248    onNetworkEvent: event => {
    249      info("received a network event");
    250      eventsCount++;
    251      return createNetworkEventOwner(event);
    252    },
    253  });
    254 
    255  const overrideFile = getChromeDir(getResolvedURI(gTestPath));
    256  overrideFile.append(OVERRIDE_FILENAME);
    257  info(" override " + url + " to " + overrideFile.path + "\n");
    258  networkObserver.override(url, overrideFile.path);
    259 
    260  const overriddenText = await getResponseText(url, browser);
    261  is(
    262    overriddenText,
    263    `"use strict";\ndocument.title = "Override script loaded";\n`,
    264    "the response content for the CSP script has been overriden"
    265  );
    266  const cachedOverriddenText = await getResponseText(url, browser);
    267  is(
    268    cachedOverriddenText,
    269    `"use strict";\ndocument.title = "Override script loaded";\n`,
    270    "the cached response content for the CSP script has been overriden"
    271  );
    272 
    273  await SpecialPowers.spawn(browser, [url], async _url => {
    274    const script = content.document.createElement("script");
    275    const onLoad = new Promise(resolve =>
    276      script.addEventListener("load", resolve, { once: true })
    277    );
    278    script.src = _url;
    279    content.document.body.appendChild(script);
    280    await onLoad;
    281    is(
    282      content.document.title,
    283      "Override script loaded",
    284      "The <script> tag content  for the CSP script has been overriden and correctly evaluated"
    285    );
    286  });
    287 
    288  await BrowserTestUtils.waitForCondition(() => eventsCount >= 1);
    289 
    290  info("Remove the override for " + url);
    291  networkObserver.removeOverride(url);
    292  const restoredText = await getResponseText(url, browser);
    293  is(
    294    restoredText,
    295    `"use strict";\ndocument.title = "CSP script to override loaded";\n`,
    296    "the response content for the CSP script is back to the original one"
    297  );
    298 
    299  networkObserver.destroy();
    300 });
    301 
    302 /**
    303 * Retrieve the text content for a request to the provided url, as fetched by
    304 * the provided browser.
    305 *
    306 * @param {string} url
    307 *     The URL of the request to fetch.
    308 * @param {Browser} browser
    309 *     The content browser where the request should be fetched.
    310 * @returns {string}
    311 *     The text content of the fetch request.
    312 */
    313 async function getResponseText(url, browser) {
    314  return SpecialPowers.spawn(browser, [url], async _url => {
    315    const response = await content.wrappedJSObject.fetch(_url);
    316    return await response.text();
    317  });
    318 }