-
Notifications
You must be signed in to change notification settings - Fork 0
/
transform.ts
83 lines (69 loc) · 2.42 KB
/
transform.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// 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.ts";
import {
equalsCaseInsensitive,
hasToken,
RangeUnit as Unit,
RequestedRangeNotSatisfiableResponse,
} from "./utils.ts";
import { type Range, RangeUnit } from "./types.ts";
export type UnitLike =
| RangeUnit
| readonly [RangeUnit, ...readonly RangeUnit[]];
export function withAcceptRanges(
response: Response,
unit: UnitLike,
): Response {
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 interface Context {
readonly ranges: Iterable<Range>;
readonly rangeValue: string;
}
export async function withContentRange(
response: Response,
context: Context,
): Promise<Response> {
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);
}