range-sw.js (7585B)
1 importScripts('/resources/testharness.js'); 2 3 setup({ explicit_done: true }); 4 5 function assert_range_request(request, expectedRangeHeader, name) { 6 assert_equals(request.headers.get('Range'), expectedRangeHeader, name); 7 } 8 9 async function broadcast(msg) { 10 for (const client of await clients.matchAll()) { 11 client.postMessage(msg); 12 } 13 } 14 15 addEventListener('fetch', async event => { 16 /** @type Request */ 17 const request = event.request; 18 const url = new URL(request.url); 19 const action = url.searchParams.get('action'); 20 21 switch (action) { 22 case 'range-header-filter-test': 23 rangeHeaderFilterTest(request); 24 return; 25 case 'range-header-passthrough-test': 26 rangeHeaderPassthroughTest(event); 27 return; 28 case 'store-ranged-response': 29 storeRangedResponse(event); 30 return; 31 case 'use-stored-ranged-response': 32 useStoredRangeResponse(event); 33 return; 34 case 'broadcast-accept-encoding': 35 broadcastAcceptEncoding(event); 36 return; 37 case 'record-media-range-request': 38 return recordMediaRangeRequest(event); 39 case 'use-media-range-request': 40 useMediaRangeRequest(event); 41 return; 42 } 43 }); 44 45 /** 46 * @param {Request} request 47 */ 48 function rangeHeaderFilterTest(request) { 49 const rangeValue = request.headers.get('Range'); 50 51 test(() => { 52 assert_range_request(new Request(request), rangeValue, `Untampered`); 53 assert_range_request(new Request(request, {}), rangeValue, `Untampered (no init props set)`); 54 assert_range_request(new Request(request, { __foo: 'bar' }), rangeValue, `Untampered (only invalid props set)`); 55 assert_range_request(new Request(request, { mode: 'cors' }), rangeValue, `More permissive mode`); 56 assert_range_request(request.clone(), rangeValue, `Clone`); 57 }, "Range headers correctly preserved"); 58 59 test(() => { 60 assert_range_request(new Request(request, { headers: { Range: 'foo' } }), null, `Tampered - range header set`); 61 assert_range_request(new Request(request, { headers: {} }), null, `Tampered - empty headers set`); 62 assert_range_request(new Request(request, { mode: 'no-cors' }), null, `Tampered – mode set`); 63 assert_range_request(new Request(request, { cache: 'no-cache' }), null, `Tampered – cache mode set`); 64 }, "Range headers correctly removed"); 65 66 test(() => { 67 let headers; 68 69 headers = new Request(request).headers; 70 headers.delete('does-not-exist'); 71 assert_equals(headers.get('Range'), rangeValue, `Preserved if no header actually removed`); 72 73 headers = new Request(request).headers; 74 headers.append('foo', 'bar'); 75 assert_equals(headers.get('Range'), rangeValue, `Preserved if silent-failure on append (due to request-no-cors guard)`); 76 77 headers = new Request(request).headers; 78 headers.set('foo', 'bar'); 79 assert_equals(headers.get('Range'), rangeValue, `Preserved if silent-failure on set (due to request-no-cors guard)`); 80 81 headers = new Request(request).headers; 82 headers.append('Range', 'foo'); 83 assert_equals(headers.get('Range'), rangeValue, `Preserved if silent-failure on append (due to request-no-cors guard)`); 84 85 headers = new Request(request).headers; 86 headers.set('Range', 'foo'); 87 assert_equals(headers.get('Range'), rangeValue, `Preserved if silent-failure on set (due to request-no-cors guard)`); 88 89 headers = new Request(request).headers; 90 headers.append('Accept', 'whatever'); 91 assert_equals(headers.get('Range'), null, `Stripped if header successfully appended`); 92 93 headers = new Request(request).headers; 94 headers.set('Accept', 'whatever'); 95 assert_equals(headers.get('Range'), null, `Stripped if header successfully set`); 96 97 headers = new Request(request).headers; 98 headers.delete('Accept'); 99 assert_equals(headers.get('Range'), null, `Stripped if header successfully deleted`); 100 101 headers = new Request(request).headers; 102 headers.delete('Range'); 103 assert_equals(headers.get('Range'), null, `Stripped if range header successfully deleted`); 104 }, "Headers correctly filtered"); 105 106 done(); 107 } 108 109 function rangeHeaderPassthroughTest(event) { 110 /** @type Request */ 111 const request = event.request; 112 const url = new URL(request.url); 113 const key = url.searchParams.get('range-received-key'); 114 115 event.waitUntil(new Promise(resolve => { 116 promise_test(async () => { 117 await fetch(event.request); 118 const response = await fetch('stash-take.py?key=' + key); 119 assert_equals(await response.json(), 'range-header-received'); 120 resolve(); 121 }, `Include range header in network request`); 122 123 done(); 124 })); 125 126 // Just send back any response, it isn't important for the test. 127 event.respondWith(new Response('')); 128 } 129 130 let storedRangeResponseP; 131 132 function storeRangedResponse(event) { 133 /** @type Request */ 134 const request = event.request; 135 const id = new URL(request.url).searchParams.get('id'); 136 137 storedRangeResponseP = fetch(event.request); 138 broadcast({ id }); 139 140 // Just send back any response, it isn't important for the test. 141 event.respondWith(new Response('')); 142 } 143 144 function useStoredRangeResponse(event) { 145 event.respondWith(async function() { 146 const response = await storedRangeResponseP; 147 if (!response) throw Error("Expected stored range response"); 148 return response.clone(); 149 }()); 150 } 151 152 function broadcastAcceptEncoding(event) { 153 /** @type Request */ 154 const request = event.request; 155 const id = new URL(request.url).searchParams.get('id'); 156 157 broadcast({ 158 id, 159 acceptEncoding: request.headers.get('Accept-Encoding') 160 }); 161 162 // Just send back any response, it isn't important for the test. 163 event.respondWith(new Response('')); 164 } 165 166 let rangeResponse = {}; 167 168 async function recordMediaRangeRequest(event) { 169 /** @type Request */ 170 const request = event.request; 171 const url = new URL(request.url); 172 const urlParams = new URLSearchParams(url.search); 173 const size = urlParams.get("size"); 174 const id = urlParams.get('id'); 175 const key = 'size' + size; 176 177 if (key in rangeResponse) { 178 // Don't re-fetch ranges we already have. 179 const clonedResponse = rangeResponse[key].clone(); 180 event.respondWith(clonedResponse); 181 } else if (event.request.headers.get("range") === "bytes=0-") { 182 // Generate a bogus 206 response to trigger subsequent range requests 183 // of the desired size. 184 const length = urlParams.get("length") + 100; 185 const body = "A".repeat(Number(size)); 186 event.respondWith(new Response(body, {status: 206, headers: { 187 "Content-Type": "audio/mp4", 188 "Content-Range": `bytes 0-1/${length}` 189 }})); 190 } else if (event.request.headers.get("range") === `bytes=${Number(size)}-`) { 191 // Pass through actual range requests which will attempt to fetch up to the 192 // length in the original response which is bigger than the actual resource 193 // to make sure 206 and 416 responses are treated the same. 194 rangeResponse[key] = await fetch(event.request); 195 196 // Let the client know we have the range response for the given ID 197 broadcast({id}); 198 } else { 199 event.respondWith(Promise.reject(Error("Invalid Request"))); 200 } 201 } 202 203 function useMediaRangeRequest(event) { 204 /** @type Request */ 205 const request = event.request; 206 const url = new URL(request.url); 207 const urlParams = new URLSearchParams(url.search); 208 const size = urlParams.get("size"); 209 const key = 'size' + size; 210 211 // Send a clone of the range response to preload. 212 if (key in rangeResponse) { 213 const clonedResponse = rangeResponse[key].clone(); 214 event.respondWith(clonedResponse); 215 } else { 216 event.respondWith(Promise.reject(Error("Invalid Request"))); 217 } 218 }