tor-browser

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

network-partition-key.html (8987B)


      1 <!doctype html>
      2 <html>
      3 <head>
      4  <meta charset="utf-8">
      5  <title>Connection partitioning by site</title>
      6  <meta name="help" href="https://fetch.spec.whatwg.org/#network-partition-keys">
      7  <meta name="timeout" content="long">
      8  <script src="/resources/testharness.js"></script>
      9  <script src="/resources/testharnessreport.js"></script>
     10  <script src="/common/utils.js"></script>
     11  <script src="/common/get-host-info.sub.js"></script>
     12 </head>
     13 <body>
     14 <!-- Used to open about:blank tabs from opaque origins -->
     15 <iframe id="iframe0" sandbox="allow-popups allow-scripts allow-popups-to-escape-sandbox"></iframe>
     16 <iframe id="iframe1" sandbox="allow-popups allow-scripts allow-popups-to-escape-sandbox"></iframe>
     17 <script>
     18 const host = get_host_info();
     19 
     20 // These two origins must correspond to different sites for this test to pass.
     21 const POPUP_ORIGINS = [
     22  host.ORIGIN,
     23  host.HTTP_NOTSAMESITE_ORIGIN
     24 ];
     25 
     26 // This origin should ideally correspond to a different site from the two above, but the
     27 // tests will still pass if it matches the site of one of the other two origins.
     28 const OTHER_ORIGIN = host.REMOTE_ORIGIN;
     29 
     30 // Except for the csp_sandbox and about:blanks, each test opens up two windows, one at
     31 // POPUP_ORIGINS[0], one at POPUP_ORIGINS[1], and has them request subresources from
     32 // subresource_origin. All requests (HTML, JS, and fetch requests) for each window go
     33 // through network-partition-key.py and have a partition_id parameter, which is used
     34 // to check if any request for one window uses the same socket as a request for the
     35 // other window.
     36 //
     37 // Whenever requests from the two different popup windows use the same connection, the
     38 // fetch requests all start returning 400 errors, but other requests will continue to
     39 // succeed, to make for clearer errors.
     40 //
     41 // include_credentials indicates whether the fetch requests use credentials or not,
     42 // which is interesting as uncredentialed sockets have separate connection pools.
     43 const tests = [
     44  {
     45    name: 'With credentials',
     46    subresource_origin: POPUP_ORIGINS[0],
     47    include_credentials: true,
     48    popup_params: [
     49      {type: 'main_frame'},
     50      {type: 'main_frame'}
     51    ]
     52  },
     53  {
     54    name: 'Without credentials',
     55    subresource_origin: POPUP_ORIGINS[0],
     56    include_credentials: false,
     57    popup_params: [
     58      {type: 'main_frame'},
     59      {type: 'main_frame'}
     60    ]
     61  },
     62  {
     63    name: 'Cross-site resources with credentials',
     64    subresource_origin: OTHER_ORIGIN,
     65    include_credentials: true,
     66    popup_params: [
     67      {type: 'main_frame'},
     68      {type: 'main_frame'}
     69    ]
     70  },
     71  {
     72    name: 'Cross-site resources without credentials',
     73    subresource_origin: OTHER_ORIGIN,
     74    include_credentials: false,
     75    popup_params: [
     76      {type: 'main_frame'},
     77      {type: 'main_frame'}
     78    ]
     79  },
     80  {
     81    name: 'Iframes',
     82    subresource_origin: OTHER_ORIGIN,
     83    include_credentials: true,
     84    popup_params: [
     85      {
     86        type: 'iframe',
     87        iframe_origin: OTHER_ORIGIN
     88      },
     89      {
     90        type: 'iframe',
     91        iframe_origin: OTHER_ORIGIN
     92      }
     93    ]
     94  },
     95  {
     96    name: 'Workers',
     97    subresource_origin: POPUP_ORIGINS[0],
     98    include_credentials: true,
     99    popup_params: [
    100      {type: 'worker'},
    101      {type: 'worker'}
    102    ]
    103  },
    104  {
    105    name: 'Workers with cross-site resources',
    106    subresource_origin: OTHER_ORIGIN,
    107    include_credentials: true,
    108    popup_params: [
    109      {type: 'worker'},
    110      {type: 'worker'}
    111    ]
    112  },
    113  {
    114    name: 'CSP sandbox',
    115    subresource_origin: POPUP_ORIGINS[0],
    116    include_credentials: true,
    117    popup_params: [
    118      {type: 'csp_sandbox'},
    119      {type: 'csp_sandbox'}
    120    ]
    121  },
    122  {
    123    name: 'about:blank from opaque origin iframe',
    124    subresource_origin: OTHER_ORIGIN,
    125    include_credentials: true,
    126    popup_params: [
    127      {type: 'opaque_about_blank'},
    128      {type: 'opaque_about_blank'}
    129    ]
    130  },
    131 ];
    132 
    133 const BASE_PATH = window.location.pathname.replace(/\/[^\/]*$/, '/');
    134 
    135 function create_script_url(origin, uuid, partition_id, dispatch) {
    136  return `${origin}${BASE_PATH}resources/network-partition-key.py?uuid=${uuid}&partition_id=${partition_id}&dispatch=${dispatch}`
    137 }
    138 
    139 function run_test(test) {
    140  var uuid = token();
    141 
    142  // Used to track the opened popup windows, so they can be closed at the end of the test.
    143  // They could be closed immediately after use, but safest to keep them open, as browsers
    144  // could use closing a window as a hint to close idle sockets that the window used.
    145  var popup_windows = [];
    146 
    147  // Creates a popup window at |url| and waits for a test result. Returns a promise.
    148  function create_popup_and_wait_for_result(url) {
    149    return new Promise(function(resolve, reject) {
    150      popup_windows.push(window.open(url));
    151      // Listen for the result
    152      function message_listener(event) {
    153        if (event.data.result === 'success') {
    154          resolve();
    155        } else if (event.data.result === 'error') {
    156          reject(event.data.details);
    157        } else {
    158          reject('Unexpected message.');
    159        }
    160      }
    161      window.addEventListener('message', message_listener, {once: 'true'});
    162    });
    163  }
    164 
    165  // Navigates iframe to url and waits for a test result. Returns a promise.
    166  function navigate_iframe_and_wait_for_result(iframe, url) {
    167    return new Promise(function(resolve, reject) {
    168      iframe.src = url;
    169      // Listen for the result
    170      function message_listener(event) {
    171        if (event.data.result === 'success') {
    172          resolve();
    173        } else if (event.data.result === 'error') {
    174          reject(event.data.details);
    175        } else {
    176          reject('Unexpected message.');
    177        }
    178      }
    179      window.addEventListener('message', message_listener, {once: 'true'});
    180    });
    181  }
    182 
    183  function make_test_function(test, index) {
    184    var popup_params = test.popup_params[index];
    185    return function() {
    186      var popup_path;
    187      var additional_url_params = '';
    188      var origin = POPUP_ORIGINS[index];
    189      var partition_id = POPUP_ORIGINS[index];
    190      if (popup_params.type == 'main_frame') {
    191        popup_path = 'resources/network-partition-checker.html';
    192      } else if (popup_params.type == 'iframe') {
    193        popup_path = 'resources/network-partition-iframe-checker.html';
    194        additional_url_params = `&other_origin=${popup_params.iframe_origin}`;
    195      } else if (popup_params.type == 'worker') {
    196        popup_path = 'resources/network-partition-worker-checker.html';
    197        // The origin of the dedicated worker must mutch the page that loads it.
    198        additional_url_params = `&other_origin=${POPUP_ORIGINS[index]}`;
    199      } else if (popup_params.type == 'csp_sandbox') {
    200        // For the Content-Security-Policy sandbox test, all requests are from the same origin, but
    201        // the origin should be treated as an opaque origin, so sockets should not be reused.
    202        origin = test.subresource_origin;
    203        partition_id = index;
    204        popup_path = 'resources/network-partition-checker.html';
    205        // Don't check partition of root document, since the document isn't sandboxed until the
    206        // root document is fetched.
    207        additional_url_params = '&sandbox=true&nocheck_partition=true'
    208      } else if (popup_params.type=='opaque_about_blank') {
    209        popup_path = 'resources/network-partition-about-blank-checker.html';
    210      } else if (popup_params.type == 'iframe') {
    211        throw 'Unrecognized popup_params.type.';
    212      }
    213      var url = create_script_url(origin, uuid, partition_id, 'fetch_file');
    214      url += `&subresource_origin=${test.subresource_origin}`
    215      url += `&include_credentials=${test.include_credentials}`
    216      url += `&path=${BASE_PATH.substring(1)}${popup_path}`;
    217      url += additional_url_params;
    218 
    219      if (popup_params.type=='opaque_about_blank') {
    220        return navigate_iframe_and_wait_for_result(iframe = document.getElementById('iframe' + index), url);
    221      }
    222 
    223      return create_popup_and_wait_for_result(url);
    224    }
    225  }
    226 
    227  // Takes a Promise, and cleans up state when the promise has completed, successfully or not, re-throwing
    228  // any exception from the passed in Promise.
    229  async function clean_up_when_done(promise) {
    230    var error;
    231    try {
    232      await promise;
    233    } catch (e) {
    234      error = e;
    235    }
    236 
    237    popup_windows.map(function (win) { win.close(); });
    238 
    239    try {
    240      var cleanup_url = create_script_url(host.ORIGIN, uuid, host.ORIGIN, 'clean_up');
    241      var response = await fetch(cleanup_url, {credentials: 'omit', mode: 'cors'});
    242      assert_equals(await response.text(), 'cleanup complete', `Sever state cleanup failed`);
    243    } catch (e) {
    244      // Prefer error from the passed in Promise over errors from the fetch request to clean up server state.
    245      error = error || e;
    246    }
    247    if (error)
    248      throw error;
    249  }
    250 
    251  return clean_up_when_done(
    252      make_test_function(test, 0)()
    253      .then(make_test_function(test, 1)));
    254 }
    255 
    256 tests.forEach(function (test) {
    257  promise_test(
    258      function() { return run_test(test); },
    259      test.name);
    260 })
    261 
    262 </script>
    263 </body>
    264 </html>