tor-browser

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

reporting-navigation.https.html (6461B)


      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/get-host-info.sub.js"></script>
      8 <script src="./credentialless/resources/common.js"></script>
      9 <script>
     10 const {ORIGIN, REMOTE_ORIGIN} = get_host_info();
     11 const COEP = '|header(cross-origin-embedder-policy,require-corp)';
     12 const COEP_RO =
     13  '|header(cross-origin-embedder-policy-report-only,require-corp)';
     14 const CORP_CROSS_ORIGIN =
     15  '|header(cross-origin-resource-policy,cross-origin)';
     16 const CSP_FRAME_ANCESTORS_NONE =
     17  '|header(content-security-policy,frame-ancestors \'none\')';
     18 const XFRAMEOPTIONS_DENY =
     19  '|header(x-frame-options,deny)';
     20 const FRAME_URL = `${ORIGIN}/common/blank.html?pipe=`;
     21 const REMOTE_FRAME_URL = `${REMOTE_ORIGIN}/common/blank.html?pipe=`;
     22 
     23 function checkCorpReport(report, contextUrl, blockedUrl, disposition) {
     24  assert_equals(report.type, 'coep');
     25  assert_equals(report.url, contextUrl);
     26  assert_equals(report.body.type, 'corp');
     27  assert_equals(report.body.blockedURL, blockedUrl);
     28  assert_equals(report.body.disposition, disposition);
     29  assert_equals(report.body.destination, 'iframe');
     30 }
     31 
     32 function checkCoepMismatchReport(report, contextUrl, blockedUrl, disposition) {
     33  assert_equals(report.type, 'coep');
     34  assert_equals(report.url, contextUrl);
     35  assert_equals(report.body.type, 'navigation');
     36  assert_equals(report.body.blockedURL, blockedUrl);
     37  assert_equals(report.body.disposition, disposition);
     38 }
     39 
     40 function loadFrame(document, url) {
     41  return new Promise((resolve, reject) => {
     42    const frame = document.createElement('iframe');
     43    frame.src = url;
     44    frame.onload = () => resolve(frame);
     45    frame.onerror = reject;
     46    document.body.appendChild(frame);
     47  });
     48 }
     49 
     50 // |parentSuffix| is a suffix for the parent frame URL.
     51 // When |withEmptyFrame| is true, this function creates an empty frame
     52 // between the parent and target frames.
     53 // |targetUrl| is a URL for the target frame.
     54 async function loadFrames(test, parentSuffix, withEmptyFrame, targetUrl) {
     55  const frame = await loadFrame(document, FRAME_URL + parentSuffix);
     56  test.add_cleanup(() => frame.remove());
     57  let parent;
     58  if (withEmptyFrame) {
     59    parent = frame.contentDocument.createElement('iframe');
     60    frame.contentDocument.body.appendChild(parent);
     61  } else {
     62    parent = frame;
     63  }
     64  // Here we don't need "await". This loading may or may not succeed, and
     65  // we're not interested in the result.
     66  loadFrame(parent.contentDocument, targetUrl);
     67 
     68  return parent;
     69 }
     70 
     71 async function observeReports(global, expected_count) {
     72  const reports = [];
     73  const receivedEveryReports = new Promise(resolve => {
     74    if (expected_count == 0)
     75      resolve();
     76 
     77    const observer = new global.ReportingObserver((rs) => {
     78      for (const r of rs) {
     79        reports.push(r.toJSON());
     80      }
     81      if (expected_count <= reports.length)
     82        resolve();
     83    });
     84    observer.observe();
     85 
     86  });
     87 
     88  // Wait 5000 ms more to catch additionnal unexpected reports.
     89  await receivedEveryReports;
     90  await new Promise(r => step_timeout(r, 5000));
     91  return reports;
     92 }
     93 
     94 // CASES is a list of test case. Each test case consists of:
     95 //   parent: the suffix of the URL of the parent frame.
     96 //   target: the suffix of the URL of the target frame.
     97 //   reports: the expectation of reports to be made. Each report is one of:
     98 //     'CORP': CORP violation
     99 //     'CORP-RO' CORP violation (report only)
    100 //     'NAV': COEP mismatch between the frames.
    101 //     'NAV-RO': COEP mismatch between the frames (report only).
    102 const CASES = [
    103  { parent: '', target: '', reports: [] },
    104  { parent: '', target: COEP, reports: [] },
    105  { parent: COEP, target: COEP, reports: ['CORP'] },
    106  { parent: COEP, target: '', reports: ['CORP'] },
    107 
    108  { parent: '', target: CORP_CROSS_ORIGIN, reports: [] },
    109  { parent: COEP, target: CORP_CROSS_ORIGIN, reports: ['NAV'] },
    110 
    111  { parent: '', target: COEP + CORP_CROSS_ORIGIN, reports: [] },
    112  { parent: COEP, target: COEP + CORP_CROSS_ORIGIN, reports: [] },
    113 
    114  { parent: COEP_RO, target: COEP, reports: ['CORP-RO'] },
    115  { parent: COEP_RO, target: '', reports: ['CORP-RO', 'NAV-RO'] },
    116  { parent: COEP_RO, target: CORP_CROSS_ORIGIN, reports: ['NAV-RO'] },
    117  { parent: COEP_RO, target: COEP + CORP_CROSS_ORIGIN, reports: [] },
    118 
    119  { parent: COEP, target: COEP_RO + CORP_CROSS_ORIGIN, reports: ['NAV'] },
    120 
    121  // Test ordering of CSP frame-ancestors, COEP, and X-Frame-Options
    122  { parent: COEP, target: CORP_CROSS_ORIGIN + CSP_FRAME_ANCESTORS_NONE, reports: [] },
    123  { parent: COEP, target: CORP_CROSS_ORIGIN + XFRAMEOPTIONS_DENY, reports: ['NAV'] },
    124 ];
    125 
    126 for (const testcase of CASES) {
    127  for (const withEmptyFrame of [false, true]) {
    128    function desc(s) {
    129      return s === '' ? '(none)' : s;
    130    }
    131    // These tests are very slow, so they must be run in parallel using
    132    // async_test.
    133    async_test(t => {
    134      const targetUrl = REMOTE_FRAME_URL + testcase.target;
    135      loadFrames(t, testcase.parent, withEmptyFrame, targetUrl)
    136          .then(t.step_func(parent => {
    137        const contextUrl = parent.src ? parent.src : 'about:blank';
    138        observeReports(parent.contentWindow, testcase.reports.length)
    139          .then(t.step_func(reports => {
    140            assert_equals(reports.length, testcase.reports.length);
    141            for (let i = 0; i < reports.length; i += 1) {
    142              const report = reports[i];
    143              switch (testcase.reports[i]) {
    144                case 'CORP':
    145                  checkCorpReport(report, contextUrl, targetUrl, 'enforce');
    146                  break;
    147                case 'CORP-RO':
    148                  checkCorpReport(report, contextUrl, targetUrl, 'reporting');
    149                  break;
    150                case 'NAV':
    151                  checkCoepMismatchReport(report, contextUrl, targetUrl, 'enforce');
    152                  break;
    153                case 'NAV-RO':
    154                  checkCoepMismatchReport(report, contextUrl, targetUrl, 'reporting');
    155                  break;
    156                default:
    157                  assert_unreached(
    158                    'Unexpected report expeaction: ' + testcase.reports[i]);
    159              }
    160            }
    161            t.done();
    162          })).catch(t.step_func(e => { throw e; }));
    163      })).catch(t.step_func(e => { throw e; }));
    164    }, `parent: ${desc(testcase.parent)}, target: ${desc(testcase.target)}, ` +
    165       `with empty frame: ${withEmptyFrame}`);
    166  }
    167 }
    168 
    169 </script>
    170 </body></html>