import type { NextRequest } from "next/server"; import { prisma } from "@/lib/db"; export const dynamic = "force-dynamic"; type Context = { params: Promise<{ id: string }> }; /** * Liefert eine hochgeladene Datei aus. * * Bewusst ohne Anmeldung: Die Landingpages sind öffentlich, und wer einen * Code scannt, soll das hinterlegte PDF sehen können. Der Schutz liegt in der * nicht erratbaren Id — dieselbe Überlegung wie beim Slug einer Kurz-URL. */ export async function GET( request: NextRequest, context: Context, ): Promise { const { id } = await context.params; const upload = await prisma.upload.findUnique({ where: { id } }); if (!upload) { return new Response("Datei nicht gefunden", { status: 404, headers: { "Content-Type": "text/plain; charset=utf-8" }, }); } // `?download` erzwingt das Speichern statt der Anzeige. const alsDownload = request.nextUrl.searchParams.has("download"); return new Response(new Uint8Array(upload.data), { headers: { "Content-Type": upload.mimeType, "Content-Length": String(upload.size), // Der Typ steht fest — der Browser soll nicht raten und daraus // womöglich etwas Ausführbares machen. "X-Content-Type-Options": "nosniff", // Ein PDF darf Skripte enthalten. Ausgeliefert von derselben Adresse // wie die Anwendung könnte es damit auf angemeldete Sitzungen // zugreifen; die Sandbox nimmt ihm diese Möglichkeit. "Content-Security-Policy": "sandbox; default-src 'none'; frame-ancestors 'none'", "X-Frame-Options": "DENY", "Content-Disposition": `${alsDownload ? "attachment" : "inline"}; filename*=UTF-8''${encodeURIComponent(upload.filename)}`, // Der Inhalt einer Id ändert sich nie — die Id wechselt mit der Datei. "Cache-Control": "public, max-age=31536000, immutable", }, }); }