From 2509e906f0e8f7a0961740a454ea83826c4d1176 Mon Sep 17 00:00:00 2001 From: Tomoki Miyauchi Date: Fri, 10 Mar 2023 02:37:21 +0900 Subject: [PATCH] fix(transform): fix to change 416 response condition --- transform.ts | 64 +++++++++++++++++++++++------------------------ transform_test.ts | 46 +++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 36 deletions(-) diff --git a/transform.ts b/transform.ts index 613b839..126ad83 100644 --- a/transform.ts +++ b/transform.ts @@ -11,7 +11,6 @@ import { parse, RangeHeader, type RangeSpec, - RangesSpecifier, RepresentationHeader, Status, unsafe, @@ -53,6 +52,7 @@ export async function withContentRange( request.method !== Method.Get || isNull(rangeValue) || request.headers.has(ConditionalHeader.IfRange) || + !response.ok || response.headers.has(RangeHeader.ContentRange) || response.headers.get(RangeHeader.AcceptRanges) === Unit.None || response.bodyUsed || @@ -68,23 +68,41 @@ export async function withContentRange( } const parsedRange = rangeContainer.value; - const validityResult = checkValidity(parsedRange, context.ranges); - // An origin server MUST ignore a Range header field that contains a range unit it does not understand. A proxy MAY discard a Range header field that contains a range unit it does not understand. - // @see https://www.rfc-editor.org/rfc/rfc9110#section-14.2-5 - if (!validityResult) return response; - - const matchedRange = validityResult; + const maybeRange = Array.from(context.ranges).find(matchRange); const body = await response.clone().arrayBuffer(); + + if (!maybeRange) { + // @see https://www.rfc-editor.org/rfc/rfc9110#section-14.2-13 + return new RequestedRangeNotSatisfiableResponse({ + rangeUnit: parsedRange.rangeUnit, + completeLength: body.byteLength, + }, { headers: response.headers }); + } + + const isValid = parsedRange + .rangeSet + .map(toSpecifier) + .every((v) => maybeRange.specifiers.includes(v)); + + if (!isValid) { + // @see https://www.rfc-editor.org/rfc/rfc9110#section-14.2-13 + return new RequestedRangeNotSatisfiableResponse({ + rangeUnit: parsedRange.rangeUnit, + completeLength: body.byteLength, + }, { headers: response.headers }); + } + + const matchedRange = maybeRange; const satisfiableRangeSet = parsedRange.rangeSet.filter(isSatisfiable); if (!satisfiableRangeSet.length) { return new RequestedRangeNotSatisfiableResponse({ - rangeUnit: validityResult.unit, + rangeUnit: matchedRange.unit, completeLength: body.byteLength, }, { headers: response.headers }); } - const partialContents = await validityResult.partial({ + const partialContents = await matchedRange.partial({ rangeSet: parsedRange.rangeSet, content: body, contentType, @@ -102,6 +120,10 @@ export async function withContentRange( function isSatisfiable(rangeSpec: RangeSpec): boolean { return matchedRange.isSatisfiable({ contents: body, rangeSpec }); } + + function matchRange(range: Range): boolean { + return range.unit === parsedRange.rangeUnit; + } } function toSpecifier( @@ -112,27 +134,3 @@ function toSpecifier( return "suffix-range"; } - -function checkValidity( - range: RangesSpecifier, - ranges: Iterable, -): false | Range { - const parsedRange = range; - const maybeRange = Array.from(ranges).find(matchRange); - - // An origin server MUST ignore a Range header field that contains a range unit it does not understand. A proxy MAY discard a Range header field that contains a range unit it does not understand. - // @see https://www.rfc-editor.org/rfc/rfc9110#section-14.2-5 - if (!maybeRange) return false; - - const matchedRange = maybeRange; - const nodes = parsedRange.rangeSet.map(toSpecifier); - const isValid = nodes.every((v) => matchedRange.specifiers.includes(v)); - - if (!isValid) return false; - - return maybeRange; - - function matchRange(range: Range): boolean { - return range.unit === parsedRange.rangeUnit; - } -} diff --git a/transform_test.ts b/transform_test.ts index f0463f3..0a836fb 100644 --- a/transform_test.ts +++ b/transform_test.ts @@ -50,6 +50,22 @@ describe("withContentRange", () => { assert(initResponse === response); }); + it("if the response is not ok", async () => { + const initResponse = new Response(null, { + status: Status.NotFound, + headers: { [RangeHeader.ContentRange]: "" }, + }); + const response = await withContentRange( + new Request("test:", { + headers: { [RangeHeader.Range]: "bytes=0-" }, + }), + initResponse, + { ranges: [] }, + ); + + assert(initResponse === response); + }); + it("if the Content-Range header exists in response", async () => { const initResponse = new Response(null, { headers: { [RangeHeader.ContentRange]: "" }, @@ -114,7 +130,15 @@ describe("withContentRange", () => { { ranges: [] }, ); - assert(initResponse === response); + assert( + equalsResponse( + response, + new Response(null, { + status: Status.RequestedRangeNotSatisfiable, + headers: { [RangeHeader.ContentRange]: "bytes */0" }, + }), + ), + ); }); it("if the range unit does not match", async () => { @@ -133,7 +157,15 @@ describe("withContentRange", () => { }, ); - assert(initResponse === response); + assert( + equalsResponse( + response, + new Response(null, { + status: Status.RequestedRangeNotSatisfiable, + headers: { [RangeHeader.ContentRange]: "bytes */0" }, + }), + ), + ); }); it("if the range specifier does not match", async () => { @@ -152,7 +184,15 @@ describe("withContentRange", () => { }, ); - assert(initResponse === response); + assert( + equalsResponse( + response, + new Response(null, { + status: Status.RequestedRangeNotSatisfiable, + headers: { [RangeHeader.ContentRange]: "bytes */0" }, + }), + ), + ); }); });