tor-browser

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

nel.sub.js (9836B)


      1 const reportID = "{{$id:uuid()}}";
      2 
      3 /*
      4 * NEL tests have to run serially, since the user agent maintains a global cache
      5 * of Reporting and NEL policies, and we don't want the policies for multiple
      6 * tests to interfere with each other.  These functions (along with a Python
      7 * handler in lock.py) implement a simple spin lock.
      8 */
      9 
     10 function obtainNELLock() {
     11  return fetch("/network-error-logging/support/lock.py?op=lock&reportID=" + reportID);
     12 }
     13 
     14 function releaseNELLock() {
     15  return fetch("/network-error-logging/support/lock.py?op=unlock&reportID=" + reportID);
     16 }
     17 
     18 function nel_test(callback, name, properties) {
     19  promise_test(async t => {
     20    await obtainNELLock();
     21    await assertNELIsImplemented();
     22    await clearReportingAndNELConfigurations();
     23    await callback(t);
     24    await releaseNELLock();
     25  }, name, properties);
     26 }
     27 
     28 function nel_iframe_test(callback, name, properties) {
     29  promise_test(async t => {
     30    await obtainNELLock();
     31    await assertNELIsImplemented();
     32    await clearReportingAndNELConfigurationsInIframe();
     33    await callback(t);
     34    await releaseNELLock();
     35  }, name, properties);
     36 }
     37 
     38 /*
     39 * Helper functions for constructing domain names that contain NEL policies.
     40 */
     41 function _monitoredDomain(subdomain) {
     42  if (subdomain == "www") {
     43    return "{{hosts[alt][www]}}"
     44  } else if (subdomain == "www1") {
     45    return "{{hosts[alt][www1]}}"
     46  } else if (subdomain == "www2") {
     47    return "{{hosts[alt][www2]}}"
     48  } else if (subdomain == "nonexistent") {
     49    return "{{hosts[alt][nonexistent]}}"
     50  } else {
     51    return "{{hosts[alt][]}}"
     52  }
     53 }
     54 
     55 function _getNELResourceURL(subdomain, suffix, options = {}) {
     56  return `https://${_monitoredDomain(subdomain)}:{{ports[https][0]}}/` +
     57    (options.sanitize ? "" : `network-error-logging/support/${suffix}`);
     58 }
     59 
     60 /*
     61 * Fetches a resource whose headers define a basic NEL policy (i.e., with no
     62 * include_subdomains flag).  We ensure that we request the resource from a
     63 * different origin than is used for the main test case HTML file or for report
     64 * uploads.  This minimizes the number of reports that are generated for this
     65 * policy.
     66 */
     67 
     68 function getURLForResourceWithBasicPolicy(subdomain) {
     69  return _getNELResourceURL(subdomain, "pass.png?id="+reportID+"&success_fraction=1.0");
     70 }
     71 
     72 function getURLForResourceWithBasicPolicyv1(subdomain) {
     73  return _getNELResourceURL(subdomain, "pass2.png?id="+reportID+"&success_fraction=1.0");
     74 }
     75 
     76 function getSanitizedURLForResourceWithNoPolicy(subdomain) {
     77  return _getNELResourceURL(subdomain, "no-policy-pass.png", { sanitize: true });
     78 }
     79 
     80 function fetchResourceWithBasicPolicy(subdomain) {
     81  const url = getURLForResourceWithBasicPolicy(subdomain);
     82  return fetch(url, {mode: "no-cors"});
     83 }
     84 
     85 function fetchResourceWithBasicPolicyv1(subdomain) {
     86  const url = getURLForResourceWithBasicPolicyv1(subdomain);
     87  return fetch(url, {mode: "no-cors"});
     88 }
     89 
     90 function fetchResourceWithZeroSuccessFractionPolicy(subdomain) {
     91  const url = _getNELResourceURL(subdomain, "pass.png?id="+reportID+"&success_fraction=0.0");
     92  return fetch(url, {mode: "no-cors"});
     93 }
     94 
     95 /*
     96 * Similar to the above methods, but fetch resources in an iframe. Allows matching
     97 * full context of reports sent from an iframe that's same-site relative to the domains
     98 * a policy set.
     99 */
    100 
    101 function loadResourceWithBasicPolicyInIframe(subdomain) {
    102  return loadResourceWithPolicyInIframe(
    103      getURLForResourceWithBasicPolicy(subdomain));
    104 }
    105 
    106 function loadResourceWithZeroSuccessFractionPolicyInIframe(subdomain) {
    107  return loadResourceWithPolicyInIframe(
    108      _getNELResourceURL(subdomain, "pass.png?id="+reportID+"&success_fraction=0.0"));
    109 }
    110 
    111 function clearResourceWithBasicPolicyInIframe(subdomain) {
    112  return loadResourceWithPolicyInIframe(
    113      getURLForClearingConfiguration(subdomain));
    114 }
    115 
    116 function loadResourceWithPolicyInIframe(url) {
    117  return new Promise((resolve, reject) => {
    118    const frame = document.createElement('iframe');
    119    frame.src = url;
    120    frame.onload = () => resolve(frame);
    121    frame.onerror = () => reject('failed to load ' + url);
    122    document.body.appendChild(frame);
    123  });
    124 }
    125 
    126 /*
    127 * Fetches a resource whose headers define an include_subdomains NEL policy.
    128 */
    129 
    130 function getURLForResourceWithIncludeSubdomainsPolicy(subdomain) {
    131  return _getNELResourceURL(subdomain, "subdomains-pass.png?id="+reportID);
    132 }
    133 
    134 function fetchResourceWithIncludeSubdomainsPolicy(subdomain) {
    135  const url = getURLForResourceWithIncludeSubdomainsPolicy(subdomain);
    136  return fetch(url, {mode: "no-cors"});
    137 }
    138 
    139 /*
    140 * Fetches a resource whose headers do NOT define a NEL policy.  This may or may
    141 * not generate a NEL report, depending on whether you've already successfully
    142 * requested a resource from the same origin that included a NEL policy.
    143 */
    144 
    145 function getURLForResourceWithNoPolicy(subdomain) {
    146  return _getNELResourceURL(subdomain, "no-policy-pass.png");
    147 }
    148 
    149 function fetchResourceWithNoPolicy(subdomain) {
    150  const url = getURLForResourceWithNoPolicy(subdomain);
    151  return fetch(url, {mode: "no-cors"});
    152 }
    153 
    154 /*
    155 * Fetches a resource that doesn't exist.  This may or may not generate a NEL
    156 * report, depending on whether you've already successfully requested a resource
    157 * from the same origin that included a NEL policy.
    158 */
    159 
    160 function getURLForMissingResource(subdomain) {
    161  return _getNELResourceURL(subdomain, "nonexistent.png");
    162 }
    163 
    164 function fetchMissingResource(subdomain) {
    165  const url = getURLForMissingResource(subdomain);
    166  return fetch(url, {mode: "no-cors"});
    167 }
    168 
    169 /*
    170 * Fetches a resource that can be cached without validation.
    171 */
    172 
    173 function getURLForCachedResource(subdomain) {
    174  return _getNELResourceURL(subdomain, "cached-for-one-minute.png");
    175 }
    176 
    177 function fetchCachedResource(subdomain) {
    178  const url = getURLForCachedResource(subdomain);
    179  return fetch(url, {mode: "no-cors"});
    180 }
    181 
    182 /*
    183 * Fetches a resource that can be cached but requires validation.
    184 */
    185 
    186 function getURLForValidatedCachedResource(subdomain) {
    187  return _getNELResourceURL(subdomain, "cached-with-validation.py");
    188 }
    189 
    190 function fetchValidatedCachedResource(subdomain) {
    191  const url = getURLForValidatedCachedResource(subdomain);
    192  return fetch(url, {mode: "no-cors"});
    193 }
    194 
    195 /*
    196 * Fetches a resource that redirects once before returning a successful
    197 * response.
    198 */
    199 
    200 function getURLForRedirectedResource(subdomain) {
    201  return _getNELResourceURL(subdomain, "redirect.py?id="+reportID);
    202 }
    203 
    204 function fetchRedirectedResource(subdomain) {
    205  const url = getURLForRedirectedResource(subdomain);
    206  return fetch(url, {mode: "no-cors"});
    207 }
    208 
    209 /*
    210 * Fetches resources that clear out any existing Reporting or NEL configurations
    211 * for all origins that any test case might use.
    212 */
    213 
    214 function getURLForClearingConfiguration(subdomain) {
    215  return _getNELResourceURL(subdomain, "clear-policy-pass.png?id="+reportID);
    216 }
    217 
    218 async function clearReportingAndNELConfigurations(subdomain) {
    219  await Promise.all([
    220    fetch(getURLForClearingConfiguration(""), {mode: "no-cors"}),
    221    fetch(getURLForClearingConfiguration("www"), {mode: "no-cors"}),
    222    fetch(getURLForClearingConfiguration("www1"), {mode: "no-cors"}),
    223    fetch(getURLForClearingConfiguration("www2"), {mode: "no-cors"}),
    224  ]);
    225  return;
    226 }
    227 
    228 async function clearReportingAndNELConfigurationsInIframe(subdomain) {
    229  await Promise.all([
    230    clearResourceWithBasicPolicyInIframe(""),
    231    clearResourceWithBasicPolicyInIframe("www"),
    232    clearResourceWithBasicPolicyInIframe("www1"),
    233    clearResourceWithBasicPolicyInIframe("www2"),
    234  ]);
    235  return;
    236 }
    237 
    238 /*
    239 * Returns whether all of the fields in obj1 also exist in obj2 with the same
    240 * values.  (Put another way, returns whether obj1 and obj2 are equal, ignoring
    241 * any extra fields in obj2.)
    242 */
    243 
    244 function _isSubsetOf(obj1, obj2) {
    245  for (const prop in obj1) {
    246    if (typeof obj1[prop] === 'object') {
    247      if (typeof obj2[prop] !== 'object') {
    248        return false;
    249      }
    250      if (!_isSubsetOf(obj1[prop], obj2[prop])) {
    251        return false;
    252      }
    253    } else if (obj1[prop] != obj2[prop]) {
    254      return false;
    255    }
    256  }
    257  return true;
    258 }
    259 
    260 /*
    261 * Verifies that a report was uploaded that contains all of the fields in
    262 * expected.
    263 */
    264 
    265 async function reportExists(expected, retain_reports, timeout) {
    266  if (!timeout) {
    267    timeout = document.querySelector("meta[name=timeout][content=long]") ? 50 : 1;
    268  }
    269  var reportLocation =
    270    "/reporting/resources/report.py?op=retrieve_report&timeout=" +
    271    timeout + "&reportID=" + reportID;
    272  if (retain_reports)
    273    reportLocation += "&retain=1";
    274  const response = await fetch(reportLocation);
    275  const json = await response.json();
    276  for (const report of json) {
    277    if (_isSubsetOf(expected, report)) {
    278      return true;
    279    }
    280  }
    281  return false;
    282 }
    283 
    284 /*
    285 * Verifies that reports were uploaded that contains all of the fields in
    286 * expected.
    287 */
    288 
    289 async function reportsExist(expected_reports, retain_reports) {
    290  const timeout = 10;
    291  let reportLocation =
    292    "/reporting/resources/report.py?op=retrieve_report&timeout=" +
    293    timeout + "&reportID=" + reportID;
    294  if (retain_reports)
    295    reportLocation += "&retain";
    296  // There must be the report of pass.png, so adding 1.
    297  const min_count = expected_reports.length + 1;
    298  reportLocation += "&min_count=" + min_count;
    299  const response = await fetch(reportLocation);
    300  const json = await response.json();
    301  for (const expected of expected_reports) {
    302    const found = json.some((report) => {
    303      return _isSubsetOf(expected, report);
    304    });
    305    if (!found)
    306      return false;
    307  }
    308  return true;
    309 }
    310 
    311 // this runs first to avoid testing on browsers not implementing NEL
    312 async function assertNELIsImplemented() {
    313  await fetchResourceWithBasicPolicy();
    314  // Assert that the report was generated
    315  assert_implements(await reportExists({
    316    url: getURLForResourceWithBasicPolicy(),
    317    type: "network-error"
    318  }, false, 1), "'Basic NEL support: missing network-error report'");
    319 }