| ... | @@ -161,7 +161,7 @@ app.get("/file/*", async (c, next) => { | ... | @@ -161,7 +161,7 @@ app.get("/file/*", async (c, next) => { |
| 161 | // Range requests | 161 | // Range requests |
| 162 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests | 162 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests |
| 163 | if (rangeHeader) { | 163 | if (rangeHeader) { |
| 164 | const ranges = parseRange(rangeHeader, file.size); | 164 | const ranges = http.parseRangeHeader(rangeHeader, file.size); |
| 165 | if (ranges?.[0] && ranges.length === 1) { | 165 | if (ranges?.[0] && ranges.length === 1) { |
| 166 | start = ranges[0][0]; | 166 | start = ranges[0][0]; |
| 167 | end = ranges[0][1]; | 167 | end = ranges[0][1]; |
| ... | @@ -216,39 +216,6 @@ function ifRangeOutdated(file: MediaFile, ifRangeHeader: string) { | ... | @@ -216,39 +216,6 @@ function ifRangeOutdated(file: MediaFile, ifRangeHeader: string) { |
| 216 | return new Date(ifRangeHeader) < file.date; | 216 | return new Date(ifRangeHeader) < file.date; |
| 217 | } | 217 | } |
| 218 | | 218 | |
| 219 | /** The end is inclusive */ | | |
| 220 | type Ranges = Array<[start: number, end: number]>; | | |
| 221 | | | |
| 222 | function parseRange(rangeHeader: string, fileSize: number): Ranges | null { | | |
| 223 | const [unit, ranges] = rangeHeader.split("="); | | |
| 224 | if (!ranges) return null; | | |
| 225 | if (unit !== "bytes") return null; | | |
| 226 | | | |
| 227 | const result: Array<[start: number, end: number]> = []; | | |
| 228 | const rangeParts = ranges.split(","); | | |
| 229 | | | |
| 230 | for (const range of rangeParts) { | | |
| 231 | const split = range.split("-"); | | |
| 232 | const [start, end] = split; | | |
| 233 | if (!start || !end) return null; | | |
| 234 | if (start === "" && end === "") return null; | | |
| 235 | const parsedRange: [number, number] = [ | | |
| 236 | start === "" ? fileSize - +end : +start, | | |
| 237 | end === "" ? fileSize - 1 : +end, | | |
| 238 | ]; | | |
| 239 | result.push(parsedRange); | | |
| 240 | } | | |
| 241 | | | |
| 242 | // Validate that ranges do not intersect | | |
| 243 | result.sort((a, b) => a[0] - b[0]); | | |
| 244 | for (let i = 1; i < result.length; i++) { | | |
| 245 | if (UNWRAP(result[i])[0] <= UNWRAP(result[i - 1])[1]) { | | |
| 246 | return null; | | |
| 247 | } | | |
| 248 | } | | |
| 249 | | | |
| 250 | return result; | | |
| 251 | } | | |
| 252 | | 219 | |
| 253 | function checkCotyledonCookie(c: Context) { | 220 | function checkCotyledonCookie(c: Context) { |
| 254 | const cookie = c.req.header("Cookie"); | 221 | const cookie = c.req.header("Cookie"); |
| ... | @@ -317,6 +284,7 @@ import { type Context, Hono } from "hono"; | ... | @@ -317,6 +284,7 @@ import { type Context, Hono } from "hono"; |
| 317 | import * as render from "@clo/lib/render"; | 284 | import * as render from "@clo/lib/render"; |
| 318 | import { etagMatches, serveAsset } from "#sitegen/assets"; | 285 | import { etagMatches, serveAsset } from "#sitegen/assets"; |
| 319 | import * as view from "#sitegen/view"; | 286 | import * as view from "#sitegen/view"; |
| | 287 | import * as http from "@clo/lib/http"; |
| 320 | import * as mime from "@clo/lib/mime"; | 288 | import * as mime from "@clo/lib/mime"; |
| 321 | | 289 | |
| 322 | import { getForFile, requireFriendAuth } from "#src/friend-auth.ts"; | 290 | import { getForFile, requireFriendAuth } from "#src/friend-auth.ts"; |