tor-browser

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

reporting-to-endpoint.https.html (7641B)


      1 <!doctype html>
      2 <html>
      3 <meta name="timeout" content="long">
      4 <body>
      5 <script src="/resources/testharness.js"></script>
      6 <script src="/resources/testharnessreport.js"></script>
      7 <script src="/common/utils.js"></script>
      8 <script src="/common/get-host-info.sub.js"></script>
      9 <script>
     10 // This file consists of tests for COEP reporting. The tests make COEP
     11 // violations and see whether reports are sent to the network as specified.
     12 // We only have basic tests in this file - one for each kind of reports,
     13 // because we can also test the reporting functionality with ReportingObserver,
     14 // and that way is faster, easier to debug, and less flaky.
     15 //
     16 // For more detailed tests and tests with workers, see tests in other files
     17 // such as
     18 //  - reporting-navigation.https.html
     19 //  - reporting-subresource-corp.https.html
     20 //  - cache-storage-reporting*.https.html
     21 // .
     22 
     23 const { REMOTE_ORIGIN } = get_host_info();
     24 const BASE = new URL("resources", location).pathname
     25 const FRAME_URL = `resources/reporting-empty-frame.html` +
     26  `?pipe=header(cross-origin-embedder-policy,require-corp;report-to="endpoint")` +
     27  `|header(cross-origin-embedder-policy-report-only,require-corp;report-to="report-only-endpoint")`;
     28 const WORKER_URL = `resources/shared-worker.js` +
     29  '?pipe=header(cross-origin-embedder-policy,require-corp;report-to="endpoint")' +
     30  `|header(cross-origin-embedder-policy-report-only,require-corp;report-to="report-only-endpoint")`;
     31 const REPORT_UUID = "4d8b6d86-c9a8-47c1-871b-111169a8f79c";
     32 const REPORT_ONLY_UUID = "5d7c1e33-ef88-43c2-9ca3-c67ff300b8c2";
     33 
     34 function wait(ms) {
     35  return new Promise(resolve => step_timeout(resolve, ms));
     36 }
     37 
     38 async function fetchReports(endpoint) {
     39  const res = await fetch(`resources/report.py?key=${endpoint}`, {cache: 'no-store'});
     40  if (res.status == 200) {
     41    return await res.json();
     42  }
     43  return [];
     44 }
     45 
     46 async function checkCorpReportExistence(endpoint, blockedUrl, contextUrl, destination, disposition) {
     47  blockedUrl = new URL(blockedUrl, location).href;
     48  contextUrl = new URL(contextUrl, location).href;
     49 
     50  const timeout = 3000;
     51  const retryDelay = 200;
     52  for (let i = 0; i * retryDelay < timeout; i++) {
     53    const reports = await fetchReports(endpoint);
     54    for (const report of reports) {
     55      if (report.type !== 'coep' || report.url !== contextUrl ||
     56          report.body.type !== 'corp') {
     57        continue;
     58      }
     59      if (report.body.blockedURL === blockedUrl &&
     60          report.body.disposition === disposition) {
     61        assert_equals(report.body.destination, destination);
     62        return;
     63      }
     64    }
     65    await wait(retryDelay);
     66  }
     67  assert_unreached(`A report whose blockedURL is ${blockedUrl.split("?")[0]} and url is ${contextUrl} is not found.`);
     68 }
     69 
     70 async function checkNavigationReportExistence(endpoint, blockedUrl, contextUrl, disposition) {
     71  blockedUrl = new URL(blockedUrl, location).href;
     72  contextUrl = new URL(contextUrl, location).href;
     73  const timeout = 3000;
     74  const retryDelay = 200;
     75  for (let i = 0; i * retryDelay < timeout; i++) {
     76    const reports = await fetchReports(endpoint);
     77    for (const report of reports) {
     78      if (report.type !== 'coep' || report.url !== contextUrl ||
     79          report.body.type !== 'navigation') {
     80        continue;
     81      }
     82      if (report.body.blockedURL === blockedUrl &&
     83          report.body.disposition === disposition) {
     84        return;
     85      }
     86    }
     87    await wait(retryDelay);
     88  }
     89  assert_unreached(`A report whose blockedURL is ${blockedUrl.split("?")[0]} and url is ${contextUrl} is not found.`);
     90 }
     91 
     92 promise_test(async t => {
     93  const iframe = document.createElement('iframe');
     94  t.add_cleanup(() => iframe.remove());
     95 
     96  iframe.src = FRAME_URL
     97  document.body.appendChild(iframe);
     98  await new Promise(resolve => {
     99    iframe.addEventListener('load', resolve, {once: true});
    100  });
    101 
    102  const url = `${REMOTE_ORIGIN}/common/text-plain.txt?${token()}`;
    103  const init = { mode: 'no-cors', cache: 'no-store' };
    104  // The response comes from cross-origin, and doesn't have a CORP
    105  // header, so it is blocked.
    106  iframe.contentWindow.fetch(url, init).catch(() => {});
    107 
    108  await checkCorpReportExistence(REPORT_UUID, url, iframe.src, '', 'enforce');
    109  await checkCorpReportExistence(
    110      REPORT_ONLY_UUID, url, iframe.src, '', 'reporting');
    111 }, 'subresource CORP');
    112 
    113 promise_test(async t => {
    114  const iframe = document.createElement('iframe');
    115  t.add_cleanup(() => iframe.remove());
    116 
    117  iframe.src = FRAME_URL
    118  document.body.appendChild(iframe);
    119  await new Promise(resolve => {
    120    iframe.addEventListener('load', resolve, {once: true});
    121  });
    122 
    123  const w = iframe.contentWindow;
    124 
    125  function attachFrame(url) {
    126    const frame = w.document.createElement('iframe');
    127    frame.src = url;
    128    w.document.body.appendChild(frame);
    129  }
    130 
    131  const url = `${REMOTE_ORIGIN}/common/blank.html?${token()}`;
    132  // The nested frame comes from cross-origin and doesn't have a CORP
    133  // header, so it is blocked.
    134  attachFrame(url);
    135 
    136  await checkCorpReportExistence(
    137      REPORT_UUID, url, iframe.src, 'iframe', 'enforce');
    138  await checkCorpReportExistence(
    139      REPORT_ONLY_UUID, url, iframe.src, 'iframe', 'reporting');
    140 }, 'navigation CORP');
    141 
    142 promise_test(async (t) => {
    143  const iframe = document.createElement('iframe');
    144  t.add_cleanup(() => iframe.remove());
    145 
    146  iframe.src = FRAME_URL;
    147  const targetUrl = `/common/blank.html?${token()}`;
    148  iframe.addEventListener('load', t.step_func(() => {
    149    const nested = iframe.contentDocument.createElement('iframe');
    150    nested.src = targetUrl;
    151    // |nested| doesn't have COEP whereas |iframe| has, so it is blocked.
    152    iframe.contentDocument.body.appendChild(nested);
    153  }), {once: true});
    154 
    155  document.body.appendChild(iframe);
    156 
    157  await checkNavigationReportExistence(
    158      REPORT_UUID, targetUrl, iframe.src, 'enforce');
    159  await checkNavigationReportExistence(
    160      REPORT_ONLY_UUID, targetUrl, iframe.src, 'reporting');
    161 }, 'COEP violation on nested frame navigation');
    162 
    163 promise_test(async (t) => {
    164  const iframe = document.createElement('iframe');
    165  t.add_cleanup(() => iframe.remove());
    166 
    167  iframe.src = 'resources/reporting-empty-frame-multiple-headers.html.asis';
    168  const targetUrl = `/common/blank.html?${token()}`;
    169 
    170  iframe.addEventListener('load', t.step_func(() => {
    171    const nested = iframe.contentDocument.createElement('iframe');
    172    nested.src = targetUrl;
    173    // |nested| doesn't have COEP whereas |iframe| has, so it is blocked.
    174    iframe.contentDocument.body.appendChild(nested);
    175  }), {once: true});
    176 
    177  document.body.appendChild(iframe);
    178 
    179  await checkNavigationReportExistence(
    180      REPORT_UUID, targetUrl, iframe.src, 'enforce');
    181  await checkNavigationReportExistence(
    182      REPORT_ONLY_UUID, targetUrl, iframe.src, 'reporting');
    183 
    184 }, 'Two COEP headers, split inside report-to value');
    185 
    186 // Shared worker do not support observer currently, so add test for endpoint
    187 // here.
    188 promise_test(async (t) => {
    189  const iframe = document.createElement('iframe');
    190  t.add_cleanup(() => iframe.remove());
    191 
    192  iframe.src = FRAME_URL;
    193  const targetUrl = `${REMOTE_ORIGIN}/common/blank.html?${token()}`;
    194  document.body.appendChild(iframe);
    195 
    196  const worker = new iframe.contentWindow.SharedWorker(WORKER_URL);
    197  worker.port.start();
    198  const script =
    199    `fetch('${targetUrl}', {mode: 'no-cors', cache: 'no-store'}).catch(e => {});`;
    200  worker.addEventListener('error', t.unreached_func('Worker.onerror'));
    201  worker.port.postMessage(script);
    202 
    203  await checkCorpReportExistence(
    204      REPORT_UUID, targetUrl, WORKER_URL, 'iframe', 'enforce');
    205  await checkCorpReportExistence(
    206      REPORT_ONLY_UUID, targetUrl, WORKER_URL, 'iframe', 'reporting');
    207 }, 'Shared worker fetch');
    208 
    209 </script>