tor-browser

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

fetch_tests.js (17932B)


      1 var origin = "http://mochi.test:8888";
      2 
      3 function fetchXHRWithMethod(name, method, onload, onerror, headers) {
      4  expectAsyncResult();
      5 
      6  onload =
      7    onload ||
      8    function () {
      9      my_ok(false, "XHR load should not complete successfully");
     10      finish();
     11    };
     12  onerror =
     13    onerror ||
     14    function () {
     15      my_ok(
     16        false,
     17        "XHR load for " + name + " should be intercepted successfully"
     18      );
     19      finish();
     20    };
     21 
     22  var x = new XMLHttpRequest();
     23  x.open(method, name, true);
     24  x.onload = function () {
     25    onload(x);
     26  };
     27  x.onerror = function () {
     28    onerror(x);
     29  };
     30  headers = headers || [];
     31  headers.forEach(function (header) {
     32    x.setRequestHeader(header[0], header[1]);
     33  });
     34  x.send();
     35 }
     36 
     37 var corsServerPath =
     38  "/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs";
     39 var corsServerURL = "http://example.com" + corsServerPath;
     40 
     41 function redirectURL(hops) {
     42  return (
     43    hops[0].server +
     44    corsServerPath +
     45    "?hop=1&hops=" +
     46    encodeURIComponent(JSON.stringify(hops))
     47  );
     48 }
     49 
     50 function fetchXHR(name, onload, onerror, headers) {
     51  return fetchXHRWithMethod(name, "GET", onload, onerror, headers);
     52 }
     53 
     54 fetchXHR("bare-synthesized.txt", function (xhr) {
     55  my_ok(xhr.status == 200, "load should be successful");
     56  my_ok(
     57    xhr.responseText == "synthesized response body",
     58    "load should have synthesized response"
     59  );
     60  finish();
     61 });
     62 
     63 fetchXHR("test-respondwith-response.txt", function (xhr) {
     64  my_ok(
     65    xhr.status == 200,
     66    "test-respondwith-response load should be successful"
     67  );
     68  my_ok(
     69    xhr.responseText == "test-respondwith-response response body",
     70    "load should have response"
     71  );
     72  finish();
     73 });
     74 
     75 fetchXHR("synthesized-404.txt", function (xhr) {
     76  my_ok(xhr.status == 404, "load should 404");
     77  my_ok(
     78    xhr.responseText == "synthesized response body",
     79    "404 load should have synthesized response"
     80  );
     81  finish();
     82 });
     83 
     84 fetchXHR("synthesized-headers.txt", function (xhr) {
     85  my_ok(xhr.status == 200, "load should be successful");
     86  my_ok(
     87    xhr.getResponseHeader("X-Custom-Greeting") === "Hello",
     88    "custom header should be set"
     89  );
     90  my_ok(
     91    xhr.responseText == "synthesized response body",
     92    "custom header load should have synthesized response"
     93  );
     94  finish();
     95 });
     96 
     97 fetchXHR("synthesized-redirect-real-file.txt", function (xhr) {
     98  dump("Got status AARRGH " + xhr.status + " " + xhr.responseText + "\n");
     99  my_ok(xhr.status == 200, "load should be successful");
    100  my_ok(
    101    xhr.responseText == "This is a real file.\n",
    102    "Redirect to real file should complete."
    103  );
    104  finish();
    105 });
    106 
    107 fetchXHR("synthesized-redirect-twice-real-file.txt", function (xhr) {
    108  my_ok(xhr.status == 200, "load should be successful");
    109  my_ok(
    110    xhr.responseText == "This is a real file.\n",
    111    "Redirect to real file (twice) should complete."
    112  );
    113  finish();
    114 });
    115 
    116 fetchXHR("synthesized-redirect-synthesized.txt", function (xhr) {
    117  my_ok(xhr.status == 200, "synth+redirect+synth load should be successful");
    118  my_ok(
    119    xhr.responseText == "synthesized response body",
    120    "load should have redirected+synthesized response"
    121  );
    122  finish();
    123 });
    124 
    125 fetchXHR("synthesized-redirect-twice-synthesized.txt", function (xhr) {
    126  my_ok(
    127    xhr.status == 200,
    128    "synth+redirect+synth (twice) load should be successful"
    129  );
    130  my_ok(
    131    xhr.responseText == "synthesized response body",
    132    "load should have redirected+synthesized (twice) response"
    133  );
    134  finish();
    135 });
    136 
    137 fetchXHR("redirect.sjs", function (xhr) {
    138  my_ok(xhr.status == 404, "redirected load should be uninterrupted");
    139  finish();
    140 });
    141 
    142 fetchXHR("ignored.txt", function (xhr) {
    143  my_ok(xhr.status == 404, "load should be uninterrupted");
    144  finish();
    145 });
    146 
    147 fetchXHR("rejected.txt", null, function (xhr) {
    148  my_ok(xhr.status == 0, "load should not complete");
    149  finish();
    150 });
    151 
    152 fetchXHR("nonresponse.txt", null, function (xhr) {
    153  my_ok(xhr.status == 0, "load should not complete");
    154  finish();
    155 });
    156 
    157 fetchXHR("nonresponse2.txt", null, function (xhr) {
    158  my_ok(xhr.status == 0, "load should not complete");
    159  finish();
    160 });
    161 
    162 fetchXHR("nonpromise.txt", null, function (xhr) {
    163  my_ok(xhr.status == 0, "load should not complete");
    164  finish();
    165 });
    166 
    167 fetchXHR(
    168  "headers.txt",
    169  function (xhr) {
    170    my_ok(xhr.status == 200, "load should be successful");
    171    my_ok(xhr.responseText == "1", "request header checks should have passed");
    172    finish();
    173  },
    174  null,
    175  [
    176    ["X-Test1", "header1"],
    177    ["X-Test2", "header2"],
    178  ]
    179 );
    180 
    181 fetchXHR("http://user:pass@mochi.test:8888/user-pass", function (xhr) {
    182  my_ok(xhr.status == 200, "load should be successful");
    183  my_ok(
    184    xhr.responseText == "http://user:pass@mochi.test:8888/user-pass",
    185    "The username and password should be preserved"
    186  );
    187  finish();
    188 });
    189 
    190 fetchXHR("readable-stream.txt", function (xhr) {
    191  my_ok(xhr.status == 200, "loading completed");
    192  my_ok(xhr.responseText == "Hello!", "The message is correct!");
    193  finish();
    194 });
    195 
    196 fetchXHR(
    197  "readable-stream-locked.txt",
    198  function (xhr) {
    199    my_ok(false, "This should not be called!");
    200    finish();
    201  },
    202  function () {
    203    my_ok(true, "The exception has been correctly handled!");
    204    finish();
    205  }
    206 );
    207 
    208 fetchXHR(
    209  "readable-stream-with-exception.txt",
    210  function (xhr) {
    211    my_ok(false, "This should not be called!");
    212    finish();
    213  },
    214  function () {
    215    my_ok(true, "The exception has been correctly handled!");
    216    finish();
    217  }
    218 );
    219 
    220 fetchXHR(
    221  "readable-stream-with-exception2.txt",
    222  function (xhr) {
    223    my_ok(false, "This should not be called!");
    224    finish();
    225  },
    226  function () {
    227    my_ok(true, "The exception has been correctly handled!");
    228    finish();
    229  }
    230 );
    231 
    232 fetchXHR(
    233  "readable-stream-already-consumed.txt",
    234  function (xhr) {
    235    my_ok(false, "This should not be called!");
    236    finish();
    237  },
    238  function () {
    239    my_ok(true, "The exception has been correctly handled!");
    240    finish();
    241  }
    242 );
    243 
    244 var expectedUncompressedResponse = "";
    245 for (let i = 0; i < 10; ++i) {
    246  expectedUncompressedResponse += "hello";
    247 }
    248 expectedUncompressedResponse += "\n";
    249 
    250 // ServiceWorker does not intercept, at which point the network request should
    251 // be correctly decoded.
    252 fetchXHR("deliver-gzip.sjs", function (xhr) {
    253  my_ok(xhr.status == 200, "network gzip load should be successful");
    254  my_ok(
    255    xhr.responseText == expectedUncompressedResponse,
    256    "network gzip load should have synthesized response."
    257  );
    258  my_ok(
    259    xhr.getResponseHeader("Content-Encoding") == "gzip",
    260    "network Content-Encoding should be gzip."
    261  );
    262  my_ok(
    263    xhr.getResponseHeader("Content-Length") == "35",
    264    "network Content-Length should be of original gzipped file."
    265  );
    266  finish();
    267 });
    268 
    269 fetchXHR("hello.gz", function (xhr) {
    270  my_ok(xhr.status == 200, "gzip load should be successful");
    271  my_ok(
    272    xhr.responseText == expectedUncompressedResponse,
    273    "gzip load should have synthesized response."
    274  );
    275  my_ok(
    276    xhr.getResponseHeader("Content-Encoding") == "gzip",
    277    "Content-Encoding should be gzip."
    278  );
    279  my_ok(
    280    xhr.getResponseHeader("Content-Length") == "35",
    281    "Content-Length should be of original gzipped file."
    282  );
    283  finish();
    284 });
    285 
    286 fetchXHR("hello-after-extracting.gz", function (xhr) {
    287  my_ok(xhr.status == 200, "gzip load after extracting should be successful");
    288  my_ok(
    289    xhr.responseText == expectedUncompressedResponse,
    290    "gzip load after extracting should have synthesized response."
    291  );
    292  my_ok(
    293    xhr.getResponseHeader("Content-Encoding") == "gzip",
    294    "Content-Encoding after extracting should be gzip."
    295  );
    296  my_ok(
    297    xhr.getResponseHeader("Content-Length") == "35",
    298    "Content-Length after extracting should be of original gzipped file."
    299  );
    300  finish();
    301 });
    302 
    303 fetchXHR(corsServerURL + "?status=200&allowOrigin=*", function (xhr) {
    304  my_ok(
    305    xhr.status == 200,
    306    "cross origin load with correct headers should be successful"
    307  );
    308  my_ok(
    309    xhr.getResponseHeader("access-control-allow-origin") == null,
    310    "cors headers should be filtered out"
    311  );
    312  finish();
    313 });
    314 
    315 // Verify origin header is sent properly even when we have a no-intercept SW.
    316 var uriOrigin = encodeURIComponent(origin);
    317 fetchXHR(
    318  "http://example.org" +
    319    corsServerPath +
    320    "?ignore&status=200&origin=" +
    321    uriOrigin +
    322    "&allowOrigin=" +
    323    uriOrigin,
    324  function (xhr) {
    325    my_ok(
    326      xhr.status == 200,
    327      "cross origin load with correct headers should be successful"
    328    );
    329    my_ok(
    330      xhr.getResponseHeader("access-control-allow-origin") == null,
    331      "cors headers should be filtered out"
    332    );
    333    finish();
    334  }
    335 );
    336 
    337 // Verify that XHR is considered CORS tainted even when original URL is same-origin
    338 // redirected to cross-origin.
    339 fetchXHR(
    340  redirectURL([
    341    { server: origin },
    342    { server: "http://example.org", allowOrigin: origin },
    343  ]),
    344  function (xhr) {
    345    my_ok(
    346      xhr.status == 200,
    347      "cross origin load with correct headers should be successful"
    348    );
    349    my_ok(
    350      xhr.getResponseHeader("access-control-allow-origin") == null,
    351      "cors headers should be filtered out"
    352    );
    353    finish();
    354  }
    355 );
    356 
    357 // Test that CORS preflight requests cannot be intercepted. Performs a
    358 // cross-origin XHR that the SW chooses not to intercept. This requires a
    359 // preflight request, which the SW must not be allowed to intercept.
    360 fetchXHR(
    361  corsServerURL + "?status=200&allowOrigin=*",
    362  null,
    363  function (xhr) {
    364    my_ok(
    365      xhr.status == 0,
    366      "cross origin load with incorrect headers should be a failure"
    367    );
    368    finish();
    369  },
    370  [["X-Unsafe", "unsafe"]]
    371 );
    372 
    373 // Test that CORS preflight requests cannot be intercepted. Performs a
    374 // cross-origin XHR that the SW chooses to intercept and respond with a
    375 // cross-origin fetch. This requires a preflight request, which the SW must not
    376 // be allowed to intercept.
    377 fetchXHR(
    378  "http://example.org" + corsServerPath + "?status=200&allowOrigin=*",
    379  null,
    380  function (xhr) {
    381    my_ok(
    382      xhr.status == 0,
    383      "cross origin load with incorrect headers should be a failure"
    384    );
    385    finish();
    386  },
    387  [["X-Unsafe", "unsafe"]]
    388 );
    389 
    390 // Test that when the page fetches a url the controlling SW forces a redirect to
    391 // another location. This other location fetch should also be intercepted by
    392 // the SW.
    393 fetchXHR("something.txt", function (xhr) {
    394  my_ok(xhr.status == 200, "load should be successful");
    395  my_ok(
    396    xhr.responseText == "something else response body",
    397    "load should have something else"
    398  );
    399  finish();
    400 });
    401 
    402 // Test fetch will internally get it's SkipServiceWorker flag set. The request is
    403 // made from the SW through fetch(). fetch() fetches a server-side JavaScript
    404 // file that force a redirect. The redirect location fetch does not go through
    405 // the SW.
    406 fetchXHR("redirect_serviceworker.sjs", function (xhr) {
    407  my_ok(xhr.status == 200, "load should be successful");
    408  my_ok(
    409    xhr.responseText == "// empty worker, always succeed!\n",
    410    "load should have redirection content"
    411  );
    412  finish();
    413 });
    414 
    415 fetchXHR(
    416  "empty-header",
    417  function (xhr) {
    418    my_ok(xhr.status == 200, "load should be successful");
    419    my_ok(
    420      xhr.responseText == "emptyheader",
    421      "load should have the expected content"
    422    );
    423    finish();
    424  },
    425  null,
    426  [["emptyheader", ""]]
    427 );
    428 
    429 expectAsyncResult();
    430 fetch(
    431  "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200&allowOrigin=*"
    432 ).then(
    433  function (res) {
    434    my_ok(res.ok, "Valid CORS request should receive valid response");
    435    my_ok(res.type == "cors", "Response type should be CORS");
    436    res.text().then(function (body) {
    437      my_ok(
    438        body === "<res>hello pass</res>\n",
    439        "cors response body should match"
    440      );
    441      finish();
    442    });
    443  },
    444  function (e) {
    445    my_ok(false, "CORS Fetch failed");
    446    finish();
    447  }
    448 );
    449 
    450 expectAsyncResult();
    451 fetch(
    452  "http://example.com/tests/dom/security/test/cors/file_CrossSiteXHR_server.sjs?status=200",
    453  { mode: "no-cors" }
    454 ).then(
    455  function (res) {
    456    my_ok(res.type == "opaque", "Response type should be opaque");
    457    my_ok(res.status == 0, "Status should be 0");
    458    res.text().then(function (body) {
    459      my_ok(body === "", "opaque response body should be empty");
    460      finish();
    461    });
    462  },
    463  function (e) {
    464    my_ok(false, "no-cors Fetch failed");
    465    finish();
    466  }
    467 );
    468 
    469 expectAsyncResult();
    470 fetch("opaque-on-same-origin").then(
    471  function (res) {
    472    my_ok(
    473      false,
    474      "intercepted opaque response for non no-cors request should fail."
    475    );
    476    finish();
    477  },
    478  function (e) {
    479    my_ok(
    480      true,
    481      "intercepted opaque response for non no-cors request should fail."
    482    );
    483    finish();
    484  }
    485 );
    486 
    487 expectAsyncResult();
    488 fetch("http://example.com/opaque-no-cors", { mode: "no-cors" }).then(
    489  function (res) {
    490    my_ok(
    491      res.type == "opaque",
    492      "intercepted opaque response for no-cors request should have type opaque."
    493    );
    494    finish();
    495  },
    496  function (e) {
    497    my_ok(
    498      false,
    499      "intercepted opaque response for no-cors request should pass."
    500    );
    501    finish();
    502  }
    503 );
    504 
    505 expectAsyncResult();
    506 fetch("http://example.com/cors-for-no-cors", { mode: "no-cors" }).then(
    507  function (res) {
    508    my_ok(
    509      res.type == "cors",
    510      "synthesize CORS response should result in outer CORS response"
    511    );
    512    finish();
    513  },
    514  function (e) {
    515    my_ok(false, "cors-for-no-cors request should not reject");
    516    finish();
    517  }
    518 );
    519 
    520 function arrayBufferFromString(str) {
    521  var arr = new Uint8Array(str.length);
    522  for (let i = 0; i < str.length; ++i) {
    523    arr[i] = str.charCodeAt(i);
    524  }
    525  return arr;
    526 }
    527 
    528 expectAsyncResult();
    529 fetch(new Request("body-simple", { method: "POST", body: "my body" }))
    530  .then(function (res) {
    531    return res.text();
    532  })
    533  .then(function (body) {
    534    my_ok(
    535      body == "my bodymy body",
    536      "the body of the intercepted fetch should be visible in the SW"
    537    );
    538    finish();
    539  });
    540 
    541 expectAsyncResult();
    542 fetch(
    543  new Request("body-arraybufferview", {
    544    method: "POST",
    545    body: arrayBufferFromString("my body"),
    546  })
    547 )
    548  .then(function (res) {
    549    return res.text();
    550  })
    551  .then(function (body) {
    552    my_ok(
    553      body == "my bodymy body",
    554      "the ArrayBufferView body of the intercepted fetch should be visible in the SW"
    555    );
    556    finish();
    557  });
    558 
    559 expectAsyncResult();
    560 fetch(
    561  new Request("body-arraybuffer", {
    562    method: "POST",
    563    body: arrayBufferFromString("my body").buffer,
    564  })
    565 )
    566  .then(function (res) {
    567    return res.text();
    568  })
    569  .then(function (body) {
    570    my_ok(
    571      body == "my bodymy body",
    572      "the ArrayBuffer body of the intercepted fetch should be visible in the SW"
    573    );
    574    finish();
    575  });
    576 
    577 expectAsyncResult();
    578 var usp = new URLSearchParams();
    579 usp.set("foo", "bar");
    580 usp.set("baz", "qux");
    581 fetch(new Request("body-urlsearchparams", { method: "POST", body: usp }))
    582  .then(function (res) {
    583    return res.text();
    584  })
    585  .then(function (body) {
    586    my_ok(
    587      body == "foo=bar&baz=quxfoo=bar&baz=qux",
    588      "the URLSearchParams body of the intercepted fetch should be visible in the SW"
    589    );
    590    finish();
    591  });
    592 
    593 expectAsyncResult();
    594 var fd = new FormData();
    595 fd.set("foo", "bar");
    596 fd.set("baz", "qux");
    597 fetch(new Request("body-formdata", { method: "POST", body: fd }))
    598  .then(function (res) {
    599    return res.text();
    600  })
    601  .then(function (body) {
    602    my_ok(
    603      body.indexOf('Content-Disposition: form-data; name="foo"\r\n\r\nbar') <
    604        body.indexOf('Content-Disposition: form-data; name="baz"\r\n\r\nqux'),
    605      "the FormData body of the intercepted fetch should be visible in the SW"
    606    );
    607    finish();
    608  });
    609 
    610 expectAsyncResult();
    611 fetch(
    612  new Request("body-blob", {
    613    method: "POST",
    614    body: new Blob(new String("my body")),
    615  })
    616 )
    617  .then(function (res) {
    618    return res.text();
    619  })
    620  .then(function (body) {
    621    my_ok(
    622      body == "my bodymy body",
    623      "the Blob body of the intercepted fetch should be visible in the SW"
    624    );
    625    finish();
    626  });
    627 
    628 expectAsyncResult();
    629 fetch("interrupt.sjs").then(
    630  function (res) {
    631    my_ok(true, "interrupted fetch succeeded");
    632    res.text().then(
    633      function (body) {
    634        my_ok(false, "interrupted fetch shouldn't have complete body");
    635        finish();
    636      },
    637      function () {
    638        my_ok(true, "interrupted fetch shouldn't have complete body");
    639        finish();
    640      }
    641    );
    642  },
    643  function (e) {
    644    my_ok(false, "interrupted fetch failed");
    645    finish();
    646  }
    647 );
    648 
    649 ["DELETE", "GET", "HEAD", "OPTIONS", "POST", "PUT"].forEach(function (method) {
    650  fetchXHRWithMethod("xhr-method-test.txt", method, function (xhr) {
    651    my_ok(xhr.status == 200, method + " load should be successful");
    652    if (method === "HEAD") {
    653      my_ok(
    654        xhr.responseText == "",
    655        method + "load should not have synthesized response"
    656      );
    657    } else {
    658      my_ok(
    659        xhr.responseText == "intercepted " + method,
    660        method + " load should have synthesized response"
    661      );
    662    }
    663    finish();
    664  });
    665 });
    666 
    667 expectAsyncResult();
    668 fetch(new Request("empty-header", { headers: { emptyheader: "" } }))
    669  .then(function (res) {
    670    return res.text();
    671  })
    672  .then(
    673    function (body) {
    674      my_ok(
    675        body == "emptyheader",
    676        "The empty header was observed in the fetch event"
    677      );
    678      finish();
    679    },
    680    function (err) {
    681      my_ok(false, "A promise was rejected with " + err);
    682      finish();
    683    }
    684  );
    685 
    686 expectAsyncResult();
    687 fetch("fetchevent-extendable")
    688  .then(function (res) {
    689    return res.text();
    690  })
    691  .then(
    692    function (body) {
    693      my_ok(body == "extendable", "FetchEvent inherits from ExtendableEvent");
    694      finish();
    695    },
    696    function (err) {
    697      my_ok(false, "A promise was rejected with " + err);
    698      finish();
    699    }
    700  );
    701 
    702 expectAsyncResult();
    703 fetch("fetchevent-request")
    704  .then(function (res) {
    705    return res.text();
    706  })
    707  .then(
    708    function (body) {
    709      my_ok(body == "non-nullable", "FetchEvent.request must be non-nullable");
    710      finish();
    711    },
    712    function (err) {
    713      my_ok(false, "A promise was rejected with " + err);
    714      finish();
    715    }
    716  );