tor-browser

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

keepalive-helper.js (7362B)


      1 // Utility functions to help testing keepalive requests.
      2 
      3 // Returns a URL to an iframe that loads a keepalive URL on iframe loaded.
      4 //
      5 // The keepalive URL points to a target that stores `token`. The token will then
      6 // be posted back on iframe loaded to the parent document.
      7 // `method` defaults to GET.
      8 // `frameOrigin` to specify the origin of the iframe to load. If not set,
      9 // default to a different site origin.
     10 // `requestOrigin` to specify the origin of the fetch request target.
     11 // `sendOn` to specify the name of the event when the keepalive request should
     12 // be sent instead of the default 'load'.
     13 // `mode` to specify the fetch request's CORS mode.
     14 // `disallowCrossOrigin` to ask the iframe to set up a server that disallows
     15 // cross origin requests.
     16 function getKeepAliveIframeUrl(token, method, {
     17  frameOrigin = 'DEFAULT',
     18  requestOrigin = '',
     19  sendOn = 'load',
     20  mode = 'cors',
     21  disallowCrossOrigin = false
     22 } = {}) {
     23  const https = location.protocol.startsWith('https');
     24  frameOrigin = frameOrigin === 'DEFAULT' ?
     25      get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN'] :
     26      frameOrigin;
     27  return `${frameOrigin}/fetch/api/resources/keepalive-iframe.html?` +
     28      `token=${token}&` +
     29      `method=${method}&` +
     30      `sendOn=${sendOn}&` +
     31      `mode=${mode}&` + (disallowCrossOrigin ? `disallowCrossOrigin=1&` : ``) +
     32      `origin=${requestOrigin}`;
     33 }
     34 
     35 // Returns a different-site URL to an iframe that loads a keepalive URL.
     36 //
     37 // By default, the keepalive URL points to a target that redirects to another
     38 // same-origin destination storing `token`. The token will then be posted back
     39 // to parent document.
     40 //
     41 // The URL redirects can be customized from `origin1` to `origin2` if provided.
     42 // Sets `withPreflight` to true to get URL enabling preflight.
     43 function getKeepAliveAndRedirectIframeUrl(
     44    token, origin1, origin2, withPreflight) {
     45  const https = location.protocol.startsWith('https');
     46  const frameOrigin =
     47      get_host_info()[https ? 'HTTPS_NOTSAMESITE_ORIGIN' : 'HTTP_NOTSAMESITE_ORIGIN'];
     48  return `${frameOrigin}/fetch/api/resources/keepalive-redirect-iframe.html?` +
     49      `token=${token}&` +
     50      `origin1=${origin1}&` +
     51      `origin2=${origin2}&` + (withPreflight ? `with-headers` : ``);
     52 }
     53 
     54 async function iframeLoaded(iframe) {
     55  return new Promise((resolve) => iframe.addEventListener('load', resolve));
     56 }
     57 
     58 // Obtains the token from the message posted by iframe after loading
     59 // `getKeepAliveAndRedirectIframeUrl()`.
     60 async function getTokenFromMessage() {
     61  return new Promise((resolve) => {
     62    window.addEventListener('message', (event) => {
     63      resolve(event.data);
     64    }, {once: true});
     65  });
     66 }
     67 
     68 // Tells if `token` has been stored in the server.
     69 async function queryToken(token) {
     70  const response = await fetch(`../resources/stash-take.py?key=${token}`);
     71  const json = await response.json();
     72  return json;
     73 }
     74 
     75 // A helper to assert the existence of `token` that should have been stored in
     76 // the server by fetching ../resources/stash-put.py.
     77 //
     78 // This function simply wait for a custom amount of time before trying to
     79 // retrieve `token` from the server.
     80 // `expectTokenExist` tells if `token` should be present or not.
     81 //
     82 // NOTE:
     83 // In order to parallelize the work, we are going to have an async_test
     84 // for the rest of the work. Note that we want the serialized behavior
     85 // for the steps so far, so we don't want to make the entire test case
     86 // an async_test.
     87 function assertStashedTokenAsync(
     88    testName, token, {expectTokenExist = true} = {}) {
     89  async_test(test => {
     90    new Promise(resolve => test.step_timeout(resolve, 3000 /*ms*/))
     91        .then(test.step_func(() => {
     92          return queryToken(token);
     93        }))
     94        .then(test.step_func(result => {
     95          if (expectTokenExist) {
     96            assert_equals(result, 'on', `token should be on (stashed).`);
     97            test.done();
     98          } else {
     99            assert_not_equals(
    100                result, 'on', `token should not be on (stashed).`);
    101            return Promise.reject(`Failed to retrieve token from server`);
    102          }
    103        }))
    104        .catch(test.step_func(e => {
    105          if (expectTokenExist) {
    106            test.unreached_func(e);
    107          } else {
    108            test.done();
    109          }
    110        }));
    111  }, testName);
    112 }
    113 
    114 /**
    115 * In an iframe, and in `load` event handler, test to fetch a keepalive URL that
    116 * involves in redirect to another URL.
    117 *
    118 * `unloadIframe` to unload the iframe before verifying stashed token to
    119 * simulate the situation that unloads after fetching. Note that this test is
    120 * different from `keepaliveRedirectInUnloadTest()` in that the latter
    121 * performs fetch() call directly in `unload` event handler, while this test
    122 * does it in `load`.
    123 */
    124 function keepaliveRedirectTest(desc, {
    125  origin1 = '',
    126  origin2 = '',
    127  withPreflight = false,
    128  unloadIframe = false,
    129  expectFetchSucceed = true,
    130 } = {}) {
    131  desc = `[keepalive][iframe][load] ${desc}` +
    132      (unloadIframe ? ' [unload at end]' : '');
    133  promise_test(async (test) => {
    134    const tokenToStash = token();
    135    const iframe = document.createElement('iframe');
    136    iframe.src = getKeepAliveAndRedirectIframeUrl(
    137        tokenToStash, origin1, origin2, withPreflight);
    138    document.body.appendChild(iframe);
    139    await iframeLoaded(iframe);
    140    assert_equals(await getTokenFromMessage(), tokenToStash);
    141    if (unloadIframe) {
    142      iframe.remove();
    143    }
    144 
    145    assertStashedTokenAsync(
    146        desc, tokenToStash, {expectTokenExist: expectFetchSucceed});
    147  }, `${desc}; setting up`);
    148 }
    149 
    150 /**
    151 * Opens a different site window, and in `unload` event handler, test to fetch
    152 * a keepalive URL that involves in redirect to another URL.
    153 */
    154 function keepaliveRedirectInUnloadTest(desc, {
    155  origin1 = '',
    156  origin2 = '',
    157  url2 = '',
    158  withPreflight = false,
    159  expectFetchSucceed = true
    160 } = {}) {
    161  desc = `[keepalive][new window][unload] ${desc}`;
    162 
    163  promise_test(async (test) => {
    164    const targetUrl =
    165        `${HTTP_NOTSAMESITE_ORIGIN}/fetch/api/resources/keepalive-redirect-window.html?` +
    166        `origin1=${origin1}&` +
    167        `origin2=${origin2}&` +
    168        `url2=${url2}&` + (withPreflight ? `with-headers` : ``);
    169    const w = window.open(targetUrl);
    170    const token = await getTokenFromMessage();
    171    w.close();
    172 
    173    assertStashedTokenAsync(
    174        desc, token, {expectTokenExist: expectFetchSucceed});
    175  }, `${desc}; setting up`);
    176 }
    177 
    178 /**
    179 * utility to create pending keepalive fetch requests
    180 * The pending request state is achieved by ensuring the server (trickle.py) does not
    181 * immediately respond to the fetch requests.
    182 * The response delay is set as a url parameter.
    183 */
    184 
    185 function createPendingKeepAliveRequest(delay, remote = false) {
    186  // trickle.py is a script that can make a delayed response to the client request
    187  const trickleRemoteURL = get_host_info().HTTPS_REMOTE_ORIGIN + '/fetch/api/resources/trickle.py?count=1&ms=';
    188  const trickleLocalURL = get_host_info().HTTP_ORIGIN + '/fetch/api/resources/trickle.py?count=1&ms=';
    189  url = remote ? trickleRemoteURL : trickleLocalURL;
    190 
    191  const body = '*'.repeat(10);
    192  return fetch(url + delay, { keepalive: true, body, method: 'POST' }).then(res => {
    193      return res.text();
    194  }).then(() => {
    195      return new Promise(resolve => step_timeout(resolve, 1));
    196  }).catch((error) => {
    197      return Promise.reject(error);;
    198  })
    199 }