/**
* Serves file dumps' uploads to the players, viewers and download links of the
* frontend, which builds the URL with `dumpFileUrl`.
*
* - `GET /api/files/:dumpId?v=&token=` — public; a private dump's file needs
* its poster's media `token`. Serves the file, inline when that is safe and
* as a download otherwise, with byte ranges.
*
* @module
*/
import { Router } from "@oak/oak";
import { APIErrorCode } from "../../shared/api.ts";
import { APIException } from "../lib/api-exception.ts";
import { DUMPS_DIR } from "../lib/upload.ts";
import { authenticateMedia } from "../lib/auth.ts";
import { serveFile } from "../lib/serve-file.ts";
import { dumpFileVersion, getDump } from "../services/dump-service.ts";
/**
* Mounted by `api/main.ts` behind the error, Open Graph and CORS middleware.
* An `APIException` thrown by a handler becomes the JSON error response there.
*/
const router: Router = new Router({ prefix: "/api/files" });
/**
* Builds a `Content-Disposition` value for a file called `name`. `Headers.set`
* throws on any character above U+00FF, so `filename` gets an ASCII copy of the
* name and `filename*` (RFC 5987) the full one, percent-encoded.
*/
function contentDisposition(
type: "inline" | "attachment",
name: string,
): string {
const asciiFallback = name
.replace(/["\\]/g, "_")
.replace(/[^\x20-\x7e]/g, "_");
return `${type}; filename="${asciiFallback}"; filename*=UTF-8''${
encodeURIComponent(name)
}`;
}
/**
* Matches the types served inline. An upload's type is whatever the uploader's
* browser claimed, and the file comes from the app's own origin, where the
* session token lives. So only types a browser shows without running code in
* them are inline, and anything else (HTML, SVG, XML…) downloads. ``,
* `