tor-browser

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

request-upload.h2.any.js (6608B)


      1 // META: global=window,worker
      2 // META: script=../resources/utils.js
      3 // META: script=/common/utils.js
      4 // META: script=/common/get-host-info.sub.js
      5 
      6 const duplex = "half";
      7 
      8 async function assertUpload(url, method, createBody, expectedBody) {
      9  const requestInit = {method};
     10  const body = createBody();
     11  if (body) {
     12    requestInit["body"] = body;
     13    requestInit.duplex = "half";
     14  }
     15  const resp = await fetch(url, requestInit);
     16  const text = await resp.text();
     17  assert_equals(text, expectedBody);
     18 }
     19 
     20 function testUpload(desc, url, method, createBody, expectedBody) {
     21  promise_test(async () => {
     22    await assertUpload(url, method, createBody, expectedBody);
     23  }, desc);
     24 }
     25 
     26 function createStream(chunks) {
     27  return new ReadableStream({
     28    start: (controller) => {
     29      for (const chunk of chunks) {
     30        controller.enqueue(chunk);
     31      }
     32      controller.close();
     33    }
     34  });
     35 }
     36 
     37 const url = RESOURCES_DIR + "echo-content.h2.py"
     38 
     39 testUpload("Fetch with POST with empty ReadableStream", url,
     40  "POST",
     41  () => {
     42    return new ReadableStream({start: controller => {
     43      controller.close();
     44    }})
     45  },
     46  "");
     47 
     48 testUpload("Fetch with POST with ReadableStream", url,
     49  "POST",
     50  () => {
     51    return new ReadableStream({start: controller => {
     52      const encoder = new TextEncoder();
     53      controller.enqueue(encoder.encode("Test"));
     54      controller.close();
     55    }})
     56  },
     57  "Test");
     58 
     59 promise_test(async (test) => {
     60  const body = new ReadableStream({start: controller => {
     61    const encoder = new TextEncoder();
     62    controller.enqueue(encoder.encode("Test"));
     63    controller.close();
     64  }});
     65  const resp = await fetch(
     66    "/fetch/connection-pool/resources/network-partition-key.py?"
     67    + `status=421&uuid=${token()}&partition_id=${self.origin}`
     68    + `&dispatch=check_partition&addcounter=true`,
     69    {method: "POST", body: body, duplex});
     70  assert_equals(resp.status, 421);
     71  const text = await resp.text();
     72  assert_equals(text, "ok. Request was sent 1 times. 1 connections were created.");
     73 }, "Fetch with POST with ReadableStream on 421 response should return the response and not retry.");
     74 
     75 promise_test(async (test) => {
     76  const request = new Request('', {
     77    body: new ReadableStream(),
     78    method: 'POST',
     79    duplex,
     80  });
     81 
     82  assert_equals(request.headers.get('Content-Type'), null, `Request should not have a content-type set`);
     83 
     84  const response = await fetch('data:a/a;charset=utf-8,test', {
     85    method: 'POST',
     86    body: new ReadableStream(),
     87    duplex,
     88  });
     89 
     90  assert_equals(await response.text(), 'test', `Response has correct body`);
     91 }, "Feature detect for POST with ReadableStream");
     92 
     93 promise_test(async (test) => {
     94  const request = new Request('data:a/a;charset=utf-8,test', {
     95    body: new ReadableStream(),
     96    method: 'POST',
     97    duplex,
     98 });
     99 
    100  assert_equals(request.headers.get('Content-Type'), null, `Request should not have a content-type set`);
    101  const response = await fetch(request);
    102  assert_equals(await response.text(), 'test', `Response has correct body`);
    103 }, "Feature detect for POST with ReadableStream, using request object");
    104 
    105 test(() => {
    106  let duplexAccessed = false;
    107 
    108  const request = new Request("", {
    109    body: new ReadableStream(),
    110    method: "POST",
    111    get duplex() {
    112      duplexAccessed = true;
    113      return "half";
    114    },
    115  });
    116 
    117  assert_equals(
    118    request.headers.get("Content-Type"),
    119    null,
    120    `Request should not have a content-type set`
    121  );
    122  assert_true(duplexAccessed, `duplex dictionary property should be accessed`);
    123 }, "Synchronous feature detect");
    124 
    125 // The asserts the synchronousFeatureDetect isn't broken by a partial implementation.
    126 // An earlier feature detect was broken by Safari implementing streaming bodies as part of Request,
    127 // but it failed when passed to fetch().
    128 // This tests ensures that UAs must not implement RequestInit.duplex and streaming request bodies without also implementing the fetch() parts.
    129 promise_test(async () => {
    130  let duplexAccessed = false;
    131 
    132  const request = new Request("", {
    133    body: new ReadableStream(),
    134    method: "POST",
    135    get duplex() {
    136      duplexAccessed = true;
    137      return "half";
    138    },
    139  });
    140 
    141  const supported =
    142    request.headers.get("Content-Type") === null && duplexAccessed;
    143 
    144  // If the feature detect fails, assume the browser is being truthful (other tests pick up broken cases here)
    145  if (!supported) return false;
    146 
    147  await assertUpload(
    148    url,
    149    "POST",
    150    () =>
    151      new ReadableStream({
    152        start: (controller) => {
    153          const encoder = new TextEncoder();
    154          controller.enqueue(encoder.encode("Test"));
    155          controller.close();
    156        },
    157      }),
    158    "Test"
    159  );
    160 }, "Synchronous feature detect fails if feature unsupported");
    161 
    162 promise_test(async (t) => {
    163  const body = createStream(["hello"]);
    164  const method = "POST";
    165  await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex }));
    166 }, "Streaming upload with body containing a String");
    167 
    168 promise_test(async (t) => {
    169  const body = createStream([null]);
    170  const method = "POST";
    171  await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex }));
    172 }, "Streaming upload with body containing null");
    173 
    174 promise_test(async (t) => {
    175  const body = createStream([33]);
    176  const method = "POST";
    177  await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex }));
    178 }, "Streaming upload with body containing a number");
    179 
    180 promise_test(async (t) => {
    181  const url = "/fetch/api/resources/authentication.py?realm=test";
    182  const body = createStream([]);
    183  const method = "POST";
    184  await promise_rejects_js(t, TypeError, fetch(url, { method, body, duplex }));
    185 }, "Streaming upload should fail on a 401 response");
    186 
    187 promise_test(async (t) => {
    188  const abortMessage = 'foo abort';
    189  let streamCancelPromise = new Promise(async res => {
    190    var stream = new ReadableStream({
    191      cancel: function(reason) {
    192        res(reason);
    193      }
    194    });
    195    let abortController = new AbortController();
    196    let fetchPromise = promise_rejects_exactly(t, abortMessage, fetch('', {
    197                                                 method: 'POST',
    198                                                 body: stream,
    199                                                 duplex: 'half',
    200                                                 signal: abortController.signal
    201                                               }));
    202    abortController.abort(abortMessage);
    203    await fetchPromise;
    204  });
    205 
    206  let cancelReason = await streamCancelPromise;
    207  assert_equals(
    208      cancelReason, abortMessage, 'ReadableStream.cancel should be called.');
    209 }, 'ReadbleStream should be closed on signal.abort');