tor-browser

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

clear-cache-helper.sub.js (7345B)


      1 /* Any copyright is dedicated to the Public Domain.
      2 * http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict"
      5 
      6 const sameOrigin =
      7  'https://{{host}}:{{ports[https][0]}}';
      8 const subdomainOrigin =
      9  'https://{{hosts[][www2]}}:{{ports[https][0]}}';
     10 const crossSiteOrigin =
     11  'https://{{hosts[alt][]}}:{{ports[https][0]}}';
     12 const subdomainCrossSiteOrigin =
     13  'https://{{hosts[alt][www2]}}:{{ports[https][0]}}';
     14 
     15 /**
     16 * Constructs a url for an intermediate "bounce" hop which represents a tracker.
     17 * @param {string} cacheHelper - Unique uuid for this test
     18 * @param {*} options - URL generation options.
     19 * @param {boolean} [options.crossSite = false] - whether domain should be a different site
     20 * @param {boolean} [options.subdomain = false] - whether the domain should start with
     21 *        a different subdomain to make a request cross origin
     22 * @param {boolean} [options.cache = false] - whether the resource should be cacheable
     23 * @param {(null|'cache'|'all')} [options.clear] - whether to send the
     24 *        Clear-Site-Data header.
     25 * @param {(null|'cache'|'all')} [options.clear_first] - whether to send the
     26 *        Clear-Site-Data header on first response
     27 * @param {string} [response] - which response to elict - defaults to "single_html". Other
     28 *        options can be found in "clear-site-data-cache.py" server helper.
     29 * @param {*} [options.iframe] - iframe same parameters as options (recursive). Only works on
     30 *        "single_html" variation of response
     31 */
     32 function getUrl(cacheHelper, {
     33    subdomain = false,
     34    crossSite = false,
     35    cache = false,
     36    clear = null,
     37    clearFirst = null,
     38    response = "single_html",
     39    iframe = null,
     40 }) {
     41    let url;
     42    if (subdomain && crossSite) {
     43        url = subdomainCrossSiteOrigin;
     44    } else if (subdomain) { // && !crossSite
     45        url = subdomainOrigin;
     46    } else if (crossSite) { // && !subdomain
     47        url = crossSiteOrigin;
     48    } else { // !crossSite && !subdomain
     49        url = sameOrigin;
     50    }
     51    url += "/clear-site-data/support/clear-site-data-cache.py";
     52    url = new URL(url);
     53    let params = new URLSearchParams();
     54    params.append("cache_helper", cacheHelper);
     55    params.append("response", response)
     56    if (clear !== null) {
     57        params.append("clear", clear);
     58    }
     59    if (clearFirst != null) {
     60        params.append("clear_first", clearFirst);
     61    }
     62    if (cache) {
     63        params.append("cache", "");
     64    }
     65    if (iframe != null) {
     66        let iframeUrl = getUrl(cacheHelper, iframe);
     67        params.append("iframe", iframeUrl);
     68    }
     69    url.search = params;
     70    return url.toString();
     71 }
     72 
     73 /**
     74 * Opens test pages sequentially, compares first and last uuid. Makes sure test cleans up properly
     75 * @param test - test clean up
     76 * @param {string} firstUuid - uuid returned by first url
     77 * @param {array[string]} testUrls - array of all urls that should be visited
     78 * @param {integer} curIdx - index in testUrls that is visited in the current function call
     79 * @param {function assert_not_equal|assert_equal} assert - function that gets passed first and last
     80 *        uuid and determines the success of the test case
     81 * @param {function} resolve - function to call when test case is complete
     82 * @param {*} options - URL generation options.
     83 */
     84 function openTestPageHelper(test, firstUuid, testUrls, curIdx, assert, resolve) {
     85    window.addEventListener("message", test.step_func(e => {
     86        let curUuid = e.data;
     87        if (firstUuid === null) {
     88            firstUuid = curUuid;
     89        }
     90 
     91        if (curIdx + 1 < testUrls.length) {
     92            openTestPageHelper(test, firstUuid, testUrls, curIdx + 1, assert, resolve);
     93        } else {
     94            // Last Step
     95            assert(firstUuid, curUuid);
     96            resolve();
     97        }
     98    }), {once: true});
     99 
    100    window.open(testUrls[curIdx]);
    101 }
    102 
    103 // Here's the set-up for this test: Step 1 and Step 2 are repeated for each param in params
    104 // Step 1 (main window) Open popup window with url generated with `getUrl`
    105 // Step 2 (first window) Message main window with potentially cached uuid and close popup
    106 // Last Step (main window): Assert first and last uuid not equal due to `clear-site-data: "cache"` header
    107 //
    108 // Basic diagram visualizing how the test works:
    109 //
    110 //     main window opens sequentially:
    111 //             (1)                  (2)                (last) = (1)
    112 //              | Step 1             | Step 3                | Step 4
    113 //              |                    |                       |
    114 //     +--------v---------+   +------v----------+     +------v-----------+
    115 //     | first / second   |   |  Clear Data?    |     |                  |
    116 //     | origin           |   |                 |     |                  |
    117 //     |                  |   |                 |     |                  |
    118 //     | +-iframe-------+ |   | +-(iframe?)---+ | ... | +-iframe-------+ |
    119 //     | | first/second | |   | | Clear Data? | |     | |              | |
    120 //     | | origin       | |   | |             | |     | |              | |
    121 //     | +-----------+--+ |   | +-------------+ |     | +-+------------+ |
    122 //     +-------------+----+   +-----------------+     +---+--------------+
    123 //                   |                                    |
    124 //                   | Step 2            +----------------+ Step 5
    125 //                   |                   |
    126 //                   v                   v
    127 //     Last Step: is uuid from (1) different from (last)?
    128 function testCacheClear(test, params, assert) {
    129    if (params.length < 2) {
    130        // fail test case
    131        return new Promise((resolve, reject) => reject());
    132    }
    133 
    134    const cacheHelper = self.crypto.randomUUID();
    135    const testUrls = params.map((param) => getUrl(cacheHelper, param));
    136 
    137    return new Promise(resolve => {
    138        openTestPageHelper(test, null, testUrls, 0, assert, resolve)
    139    });
    140 }
    141 
    142 // The tests are built on top of the back-forward-cache test harness.
    143 // Here is the steps for the tests:
    144 // 1. Open a new window and navigate to a test URL.
    145 // 2. Navigate the window to a second page.
    146 // 3. Trigger the clear-site-data header either by window.open() or loading an
    147 //    iframe from the second page.
    148 // 4. Navigate back to the first page.
    149 // 5. Assert that the first page is or is not cached.
    150 
    151 function runBfCacheClearTest(params, description) {
    152  runBfcacheTest(
    153    {
    154      targetOrigin: sameOrigin,
    155      scripts: ["/clear-site-data/support/clear-cache-helper.sub.js"],
    156      funcBeforeBackNavigation: async (getUrlParams, mode) => {
    157 
    158        const cacheHelper = self.crypto.randomUUID();
    159        const testUrl = getUrl(cacheHelper, getUrlParams);
    160 
    161        let clearingPromise;
    162        if (mode === "window") {
    163          clearingPromise = new Promise(resolve => {
    164            window.addEventListener("message", resolve, {once: true});
    165            window.open(testUrl);
    166          });
    167        } else if (mode === "iframe") {
    168          clearingPromise = new Promise(resolve => {
    169            const iframe = document.createElement("iframe");
    170            iframe.src = testUrl;
    171            document.body.appendChild(iframe);
    172            iframe.onload = resolve;
    173          });
    174        } else {
    175          throw new Error("Unsupported mode");
    176        }
    177 
    178        await clearingPromise;
    179      },
    180      argsBeforeBackNavigation: [params.getUrlParams, params.mode],
    181      ...params,
    182    },
    183    description
    184  );
    185 }