/**
* The route serving file dumps' uploads.
*
* - `GET /api/files/:dumpId?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";
/** The router serving the endpoints the module doc lists. */
const router: Router = new Router({ prefix: "/api/files" });
/**
* Builds an RFC 6266 `Content-Disposition` value that is always a valid
* ByteString. `headers.set` throws a TypeError if the value contains any
* character > U+00FF, so a filename with an emoji / accent / CJK character
* (e.g. an uploaded file) would otherwise crash the response. We provide an
* ASCII-only `filename="…"` fallback plus an RFC 5987 `filename*` with the
* full UTF-8 name for modern browsers.
*/
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)
}`;
}
/**
* The types served inline. The MIME type of an upload is whatever the
* uploader's browser claimed, and these responses come from the app's own
* origin — where the session token lives. Only types a browser renders without
* running the file's own code are served inline; anything else (text/html,
* image/svg+xml, XML, …) downloads. ,