| importScripts('/resources/testharness.js'); |
| |
| setup({ explicit_done: true }); |
| |
| function assert_range_request(request, expectedRangeHeader, name) { |
| assert_equals(request.headers.get('Range'), expectedRangeHeader, name); |
| } |
| |
| async function broadcast(msg) { |
| for (const client of await clients.matchAll()) { |
| client.postMessage(msg); |
| } |
| } |
| |
| addEventListener('fetch', async event => { |
| /** @type Request */ |
| const request = event.request; |
| const url = new URL(request.url); |
| const action = url.searchParams.get('action'); |
| |
| switch (action) { |
| case 'range-header-filter-test': |
| rangeHeaderFilterTest(request); |
| return; |
| case 'range-header-passthrough-test': |
| rangeHeaderPassthroughTest(event); |
| return; |
| case 'store-ranged-response': |
| storeRangedResponse(event); |
| return; |
| case 'use-stored-ranged-response': |
| useStoredRangeResponse(event); |
| return; |
| case 'broadcast-accept-encoding': |
| broadcastAcceptEncoding(event); |
| return; |
| case 'record-media-range-request': |
| return recordMediaRangeRequest(event); |
| case 'use-media-range-request': |
| useMediaRangeRequest(event); |
| return; |
| } |
| }); |
| |
| /** |
| * @param {Request} request |
| */ |
| function rangeHeaderFilterTest(request) { |
| const rangeValue = request.headers.get('Range'); |
| |
| test(() => { |
| assert_range_request(new Request(request), rangeValue, `Untampered`); |
| assert_range_request(new Request(request, {}), rangeValue, `Untampered (no init props set)`); |
| assert_range_request(new Request(request, { __foo: 'bar' }), rangeValue, `Untampered (only invalid props set)`); |
| assert_range_request(new Request(request, { mode: 'cors' }), rangeValue, `More permissive mode`); |
| assert_range_request(request.clone(), rangeValue, `Clone`); |
| }, "Range headers correctly preserved"); |
| |
| test(() => { |
| assert_range_request(new Request(request, { headers: { Range: 'foo' } }), null, `Tampered - range header set`); |
| assert_range_request(new Request(request, { headers: {} }), null, `Tampered - empty headers set`); |
| assert_range_request(new Request(request, { mode: 'no-cors' }), null, `Tampered – mode set`); |
| assert_range_request(new Request(request, { cache: 'no-cache' }), null, `Tampered – cache mode set`); |
| }, "Range headers correctly removed"); |
| |
| test(() => { |
| let headers; |
| |
| headers = new Request(request).headers; |
| headers.delete('does-not-exist'); |
| assert_equals(headers.get('Range'), rangeValue, `Preserved if no header actually removed`); |
| |
| headers = new Request(request).headers; |
| headers.append('foo', 'bar'); |
| assert_equals(headers.get('Range'), rangeValue, `Preserved if silent-failure on append (due to request-no-cors guard)`); |
| |
| headers = new Request(request).headers; |
| headers.set('foo', 'bar'); |
| assert_equals(headers.get('Range'), rangeValue, `Preserved if silent-failure on set (due to request-no-cors guard)`); |
| |
| headers = new Request(request).headers; |
| headers.append('Range', 'foo'); |
| assert_equals(headers.get('Range'), rangeValue, `Preserved if silent-failure on append (due to request-no-cors guard)`); |
| |
| headers = new Request(request).headers; |
| headers.set('Range', 'foo'); |
| assert_equals(headers.get('Range'), rangeValue, `Preserved if silent-failure on set (due to request-no-cors guard)`); |
| |
| headers = new Request(request).headers; |
| headers.append('Accept', 'whatever'); |
| assert_equals(headers.get('Range'), null, `Stripped if header successfully appended`); |
| |
| headers = new Request(request).headers; |
| headers.set('Accept', 'whatever'); |
| assert_equals(headers.get('Range'), null, `Stripped if header successfully set`); |
| |
| headers = new Request(request).headers; |
| headers.delete('Accept'); |
| assert_equals(headers.get('Range'), null, `Stripped if header successfully deleted`); |
| |
| headers = new Request(request).headers; |
| headers.delete('Range'); |
| assert_equals(headers.get('Range'), null, `Stripped if range header successfully deleted`); |
| }, "Headers correctly filtered"); |
| |
| done(); |
| } |
| |
| function rangeHeaderPassthroughTest(event) { |
| /** @type Request */ |
| const request = event.request; |
| const url = new URL(request.url); |
| const key = url.searchParams.get('range-received-key'); |
| |
| event.waitUntil(new Promise(resolve => { |
| promise_test(async () => { |
| await fetch(event.request); |
| const response = await fetch('stash-take.py?key=' + key); |
| assert_equals(await response.json(), 'range-header-received'); |
| resolve(); |
| }, `Include range header in network request`); |
| |
| done(); |
| })); |
| |
| // Just send back any response, it isn't important for the test. |
| event.respondWith(new Response('')); |
| } |
| |
| let storedRangeResponseP; |
| |
| function storeRangedResponse(event) { |
| /** @type Request */ |
| const request = event.request; |
| const id = new URL(request.url).searchParams.get('id'); |
| |
| storedRangeResponseP = fetch(event.request); |
| broadcast({ id }); |
| |
| // Just send back any response, it isn't important for the test. |
| event.respondWith(new Response('')); |
| } |
| |
| function useStoredRangeResponse(event) { |
| event.respondWith(async function() { |
| const response = await storedRangeResponseP; |
| if (!response) throw Error("Expected stored range response"); |
| return response.clone(); |
| }()); |
| } |
| |
| function broadcastAcceptEncoding(event) { |
| /** @type Request */ |
| const request = event.request; |
| const id = new URL(request.url).searchParams.get('id'); |
| |
| broadcast({ |
| id, |
| acceptEncoding: request.headers.get('Accept-Encoding') |
| }); |
| |
| // Just send back any response, it isn't important for the test. |
| event.respondWith(new Response('')); |
| } |
| |
| let rangeResponse = {}; |
| |
| async function recordMediaRangeRequest(event) { |
| /** @type Request */ |
| const request = event.request; |
| const url = new URL(request.url); |
| const urlParams = new URLSearchParams(url.search); |
| const size = urlParams.get("size"); |
| const id = urlParams.get('id'); |
| const key = 'size' + size; |
| |
| if (key in rangeResponse) { |
| // Don't re-fetch ranges we already have. |
| const clonedResponse = rangeResponse[key].clone(); |
| event.respondWith(clonedResponse); |
| } else if (event.request.headers.get("range") === "bytes=0-") { |
| // Generate a bogus 206 response to trigger subsequent range requests |
| // of the desired size. |
| const length = urlParams.get("length") + 100; |
| const body = "A".repeat(Number(size)); |
| event.respondWith(new Response(body, {status: 206, headers: { |
| "Content-Type": "audio/mp4", |
| "Content-Range": `bytes 0-1/${length}` |
| }})); |
| } else if (event.request.headers.get("range") === `bytes=${Number(size)}-`) { |
| // Pass through actual range requests which will attempt to fetch up to the |
| // length in the original response which is bigger than the actual resource |
| // to make sure 206 and 416 responses are treated the same. |
| rangeResponse[key] = await fetch(event.request); |
| |
| // Let the client know we have the range response for the given ID |
| broadcast({id}); |
| } else { |
| event.respondWith(Promise.reject(Error("Invalid Request"))); |
| } |
| } |
| |
| function useMediaRangeRequest(event) { |
| /** @type Request */ |
| const request = event.request; |
| const url = new URL(request.url); |
| const urlParams = new URLSearchParams(url.search); |
| const size = urlParams.get("size"); |
| const key = 'size' + size; |
| |
| // Send a clone of the range response to preload. |
| if (key in rangeResponse) { |
| const clonedResponse = rangeResponse[key].clone(); |
| event.respondWith(clonedResponse); |
| } else { |
| event.respondWith(Promise.reject(Error("Invalid Request"))); |
| } |
| } |