tor-browser

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

beacon-cors.https.window.js (5404B)


      1 // META: timeout=long
      2 // META: script=/common/get-host-info.sub.js
      3 // META: script=/common/utils.js
      4 // META: script=beacon-common.sub.js
      5 
      6 'use strict';
      7 
      8 const {HTTPS_ORIGIN, ORIGIN, HTTPS_REMOTE_ORIGIN} = get_host_info();
      9 
     10 // As /common/redirect.py is not under our control, let's make sure that
     11 // it doesn't support CORS.
     12 parallelPromiseTest(async (t) => {
     13  const destination = `${HTTPS_REMOTE_ORIGIN}/common/text-plain.txt` +
     14      `?pipe=header(access-control-allow-origin,*)`;
     15  const redirect = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` +
     16      `?location=${encodeURIComponent(destination)}`;
     17 
     18  // Fetching `destination` is fine because it supports CORS.
     19  await fetch(destination);
     20 
     21  // Fetching redirect.py should fail because it doesn't support CORS.
     22  await promise_rejects_js(t, TypeError, fetch(redirect));
     23 }, '/common/redirect.py does not support CORS');
     24 
     25 for (const type of [STRING, ARRAYBUFFER, FORM, BLOB]) {
     26  parallelPromiseTest(async (t) => {
     27    const iframe = document.createElement('iframe');
     28    document.body.appendChild(iframe);
     29    t.add_cleanup(() => iframe.remove());
     30 
     31    const payload = makePayload(SMALL, type);
     32    const id = token();
     33    // As we use "no-cors" for CORS-safelisted requests, the redirect is
     34    // processed without an error while the request is cross-origin and the
     35    // redirect handler doesn't support CORS.
     36    const destination =
     37        `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`;
     38    const url = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` +
     39        `?status=307&location=${encodeURIComponent(destination)}`;
     40 
     41    assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
     42    iframe.remove();
     43 
     44    await waitForResult(id);
     45  }, `cross-origin, CORS-safelisted: type = ${type}`);
     46 }
     47 
     48 parallelPromiseTest(async (t) => {
     49  const iframe = document.createElement('iframe');
     50  document.body.appendChild(iframe);
     51  t.add_cleanup(() => iframe.remove());
     52 
     53  const payload = makePayload(SMALL, BLOB, 'application/octet-stream');
     54  const id = token();
     55  const destination =
     56      `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`;
     57  const url = `${HTTPS_REMOTE_ORIGIN}/common/redirect.py` +
     58      `?status=307&location=${encodeURIComponent(destination)}`;
     59  assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
     60  iframe.remove();
     61 
     62  // The beacon is rejected during redirect handling because /common/redirect.py
     63  // doesn't support CORS.
     64 
     65  await new Promise((resolve) => step_timeout(resolve, 3000));
     66  const res = await fetch(`/beacon/resources/beacon.py?cmd=stat&id=${id}`);
     67  assert_equals((await res.json()).length, 0);
     68 }, `cross-origin, non-CORS-safelisted: failure case (with redirect)`);
     69 
     70 parallelPromiseTest(async (t) => {
     71  const iframe = document.createElement('iframe');
     72  document.body.appendChild(iframe);
     73  t.add_cleanup(() => iframe.remove());
     74 
     75  const payload = makePayload(SMALL, BLOB, 'application/octet-stream');
     76  const id = token();
     77  const url =
     78      `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py?cmd=store&id=${id}`;
     79  assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
     80  iframe.remove();
     81 
     82  // The beacon is rejected during preflight handling.
     83  await waitForResult(id, /*expectedError=*/ 'Preflight not expected.');
     84 }, `cross-origin, non-CORS-safelisted: failure case (without redirect)`);
     85 
     86 for (const credentials of [false, true]) {
     87  parallelPromiseTest(async (t) => {
     88    const iframe = document.createElement('iframe');
     89    document.body.appendChild(iframe);
     90    t.add_cleanup(() => iframe.remove());
     91 
     92    const payload = makePayload(SMALL, BLOB, 'application/octet-stream');
     93    const id = token();
     94    let url = `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py` +
     95        `?cmd=store&id=${id}&preflightExpected&origin=${ORIGIN}`;
     96    if (credentials) {
     97      url += `&credentials=true`;
     98    }
     99    assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
    100    iframe.remove();
    101 
    102    // We need access-control-allow-credentials in the preflight response. This
    103    // shows that the request's credentials mode is 'include'.
    104    if (credentials) {
    105      const result = await waitForResult(id);
    106      assert_equals(result.type, 'application/octet-stream');
    107    } else {
    108      await new Promise((resolve) => step_timeout(resolve, 3000));
    109      const res = await fetch(`/beacon/resources/beacon.py?cmd=stat&id=${id}`);
    110      assert_equals((await res.json()).length, 0);
    111    }
    112  }, `cross-origin, non-CORS-safelisted[credentials=${credentials}]`);
    113 }
    114 
    115 parallelPromiseTest(async (t) => {
    116  const iframe = document.createElement('iframe');
    117  document.body.appendChild(iframe);
    118  t.add_cleanup(() => iframe.remove());
    119 
    120  const payload = makePayload(SMALL, BLOB, 'application/octet-stream');
    121  const id = token();
    122  const destination = `${HTTPS_REMOTE_ORIGIN}/beacon/resources/beacon.py` +
    123      `?cmd=store&id=${id}&preflightExpected&origin=${ORIGIN}&credentials=true`;
    124  const url = `${HTTPS_REMOTE_ORIGIN}/fetch/api/resources/redirect.py` +
    125      `?redirect_status=307&allow_headers=content-type` +
    126      `&location=${encodeURIComponent(destination)}`;
    127  assert_true(iframe.contentWindow.navigator.sendBeacon(url, payload));
    128  iframe.remove();
    129 
    130  const result = await waitForResult(id);
    131  assert_equals(result.type, 'application/octet-stream');
    132 }, `cross-origin, non-CORS-safelisted success-case (with redirect)`);