@httpland/range-request-middleware
Version:
HTTP range request middleware
40 lines (39 loc) • 2.21 kB
JavaScript
// Copyright 2023-latest the httpland authors. All rights reserved. MIT license.
// This module is browser compatible.
import { distinct, isErr, isString, parseRange, RangeHeader, Status, unsafe, } from "./deps.js";
import { equalsCaseInsensitive, hasToken, RangeUnit as Unit, RequestedRangeNotSatisfiableResponse, } from "./utils.js";
export function withAcceptRanges(response, unit) {
const units = isString(unit) ? [unit] : unit;
const unitValue = distinct(units).join(", ");
if (!response.headers.has(RangeHeader.AcceptRanges)) {
response.headers.set(RangeHeader.AcceptRanges, unitValue);
}
return response;
}
export async function withContentRange(response, context) {
if (response.status !== Status.OK ||
response.headers.has(RangeHeader.ContentRange) ||
response.bodyUsed)
return response;
const acceptRanges = response.headers.get(RangeHeader.AcceptRanges);
if (isString(acceptRanges) && hasToken(acceptRanges, Unit.None)) {
return response;
}
const rangeContainer = unsafe(() => parseRange(context.rangeValue));
if (isErr(rangeContainer)) {
// A server that supports range requests MAY ignore or reject a Range header field that contains an invalid ranges-specifier (Section 14.1.1), a ranges-specifier with more than two overlapping ranges, or a set of many small ranges that are not listed in ascending order, since these are indications of either a broken client or a deliberate denial-of-service attack (Section 17.15).
// https://www.rfc-editor.org/rfc/rfc9110#section-14.2-6
return response;
}
const parsedRange = rangeContainer.value;
const matchedRange = Array.from(context.ranges).find(({ rangeUnit }) => equalsCaseInsensitive(rangeUnit, parsedRange.rangeUnit));
const body = await response.clone().arrayBuffer();
if (!matchedRange) {
// @see https://www.rfc-editor.org/rfc/rfc9110#section-14.2-13
return new RequestedRangeNotSatisfiableResponse({
rangeUnit: parsedRange.rangeUnit,
completeLength: body.byteLength,
}, { headers: response.headers });
}
return matchedRange.respond(response, parsedRange);
}