Skip to content

Commit

A broken-off body is resumed only with a rest that comes uncompressed, as its start did: the rest is joined on byte for byte, so a compressed one would have made a corrupt file.

syntaqxcommitted Parent2657efaBrowse files
2 files+12−30/2 viewed
+9−2
126126 * file's ETag gets a `206` with the rest, as registry.npmjs.org answers
127127 * one, unless `ranges` is false.
128128 */
129−function rangedUpstream(cuts: (number | null)[], options: { ranges?: boolean } = {}) {
129+function rangedUpstream(cuts: (number | null)[], options: { ranges?: boolean; encodedRest?: boolean } = {}) {
130130 const requests: Request[] = [];
131131 const send = async (request: Request) => {
132132 requests.push(request);
137137 const rest = file.slice(start);
138138 return new Response(breaking(rest, cut === null ? null : cut - start), {
139139 status: 206,
140− headers: { "content-length": String(rest.length), "content-range": `bytes ${start}-${LARGE - 1}/${LARGE}`, "accept-ranges": "bytes", etag: ETAG },
140+ headers: { "content-length": String(rest.length), "content-range": `bytes ${start}-${LARGE - 1}/${LARGE}`, "accept-ranges": "bytes", etag: ETAG, ...(options.encodedRest ? { "content-encoding": "gzip" } : {}) },
141141 });
142142 }
143143 return new Response(breaking(file, cut), {
168168 assert.deepEqual(waits, [...RETRY_WAITS_MS]);
169169 });
170170
171+test("a rest that comes compressed is never joined on to a body that came as it is", async () => {
172+ const { requests, send } = rangedUpstream([3_000_000, null], { encodedRest: true });
173+ const response = await forward(tarball(), send, sleep);
174+ await assert.rejects(response.arrayBuffer());
175+ assert.equal(requests.length, 2);
176+});
177+
171178 test("a large tarball sent whole streams as it is, asked for once", async () => {
172179 const { requests, send } = rangedUpstream([null]);
173180 const response = await forward(tarball(), send, sleep);
+3−1
7979
8080 /** Whether a `206` carries exactly the rest of the file from `offset`. */
8181 function restFrom(response: Response, offset: number, length: number): boolean {
82− if (response.status !== 206 || response.body === null) return false;
82+ // The rest is joined on byte for byte, so it must come as it is, like the start did.
83+ const encoding = (response.headers.get("content-encoding") ?? "identity").toLowerCase();
84+ if (response.status !== 206 || response.body === null || encoding !== "identity") return false;
8385 const range = /^bytes (\d+)-(\d+)\/(\d+)$/.exec((response.headers.get("content-range") ?? "").trim());
8486 return range !== null && Number(range[1]) === offset && Number(range[2]) === length - 1 && Number(range[3]) === length;
8587 }