Files
downloadwf/src/routes/download.ts
T
2026-08-15 10:42:20 +02:00

55 lines
1.7 KiB
TypeScript

import { lstat } from "node:fs/promises";
import { hasValidAuth, renderPasswordForm } from "../auth";
import { notFound } from "../http";
import { log } from "../log";
import { contentDisposition, fileIdFromKey, KEY_PATTERN, secretMatches } from "../security";
import { isExpired, type Storage } from "../storage";
export async function handleDownload(
req: Request,
storage: Storage,
rawKey: string,
secret: string,
): Promise<Response> {
if (!KEY_PATTERN.test(rawKey)) return notFound();
const id = fileIdFromKey(rawKey);
if (!id) return notFound();
const meta = await storage.readMetadata(id);
if (!meta) return notFound();
if (isExpired(meta)) {
await storage.delete(id);
log("info", `download rejected id=${id} expired`);
return notFound();
}
if (!secretMatches(meta.downloadKeyHash, rawKey)) return notFound();
if (meta.passwordHash && !hasValidAuth(req, secret, rawKey)) {
const url = new URL(req.url);
return renderPasswordForm(rawKey, meta.originalName, url.searchParams.has("e"));
}
const filePath = storage.filePath(id);
let fileStat;
try {
fileStat = await lstat(filePath);
} catch {
return notFound();
}
if (!fileStat.isFile()) return notFound();
const headers = new Headers({
"content-type": meta.mimeType || "application/octet-stream",
"content-length": String(fileStat.size),
"content-disposition": contentDisposition(meta.originalName),
"cache-control": "private, no-store",
"x-content-type-options": "nosniff",
"referrer-policy": "no-referrer",
});
if (req.method === "HEAD") return new Response(null, { status: 200, headers });
log("info", `download id=${id} size=${fileStat.size}`);
return new Response(Bun.file(filePath).stream(), { status: 200, headers });
}