69 lines
2.8 KiB
PHP
69 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use Symfony\Component\HttpFoundation\BinaryFileResponse;
|
|
|
|
/**
|
|
* Dateien, die jeder herunterladen darf: AGB, Auftragsverarbeitungsvertrag,
|
|
* technische und organisatorische Maßnahmen, Preisblätter.
|
|
*
|
|
* Sie liegen in `storage/app/files/public/` und werden ausgeliefert, wie sie
|
|
* sind. Kein Verwaltungsbereich, keine Datenbanktabelle: eine Datei ablegen
|
|
* genügt, und was nicht dort liegt, gibt es nicht.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* Warum eine eigene Adresse
|
|
* ---------------------------------------------------------------------------
|
|
*
|
|
* Diese Dateien stehen in Verträgen und auf Rechnungen. Eine Adresse, die beim
|
|
* nächsten Umbau des Portals wandert, macht aus jedem dieser Verweise einen
|
|
* toten Link — und zwar rückwirkend, in Dokumenten, die längst verschickt sind.
|
|
* Ein eigener Hostname ist die einzige Zusage, die sich einhalten lässt.
|
|
*
|
|
* ---------------------------------------------------------------------------
|
|
* Versionierte Dateinamen
|
|
* ---------------------------------------------------------------------------
|
|
*
|
|
* `agb-2026-01.pdf`, nicht `agb.pdf`. Eine Fassung, auf die ein Vertrag
|
|
* verweist, darf sich unter derselben Adresse NIE ändern — sonst zeigt der
|
|
* Verweis in einem Vertrag vom Januar plötzlich auf Bedingungen vom Juli, und
|
|
* niemand kann mehr sagen, was vereinbart war. Neue Fassung heißt neue Datei.
|
|
*
|
|
* Erzwungen wird das hier nicht — eine Regel, die eine Datei mit dem falschen
|
|
* Namen abweist, hilft niemandem, der eine hochladen will. Sie steht hier, weil
|
|
* dies die Stelle ist, an der jemand nachsieht.
|
|
*/
|
|
class PublicFileController extends Controller
|
|
{
|
|
private const ROOT = 'app/files/public';
|
|
|
|
public function __invoke(string $file): BinaryFileResponse
|
|
{
|
|
// NUR der Dateiname, ohne Pfad. `basename()` macht aus
|
|
// `../../.env` ein `.env`, und das liegt in diesem Verzeichnis nicht —
|
|
// damit ist der Pfad-Ausbruch beantwortet, bevor er anfängt. Ein
|
|
// Verzeichnisbaum unter `public/` gibt es absichtlich nicht: er wäre
|
|
// die zweite Gelegenheit für denselben Fehler.
|
|
$name = basename($file);
|
|
|
|
// Ein Punktdatei-Name kommt hier nicht durch. Sie sind in diesem
|
|
// Verzeichnis nicht vorgesehen, und ihr Vorkommen wäre ein Versehen.
|
|
if ($name === '' || str_starts_with($name, '.')) {
|
|
abort(404);
|
|
}
|
|
|
|
$path = storage_path(self::ROOT.'/'.$name);
|
|
|
|
if (! is_file($path)) {
|
|
abort(404);
|
|
}
|
|
|
|
// Inline, nicht als Anhang: eine AGB, die der Browser anzeigt, wird
|
|
// gelesen; eine, die er herunterlädt, wird weggeklickt.
|
|
return response()->file($path, [
|
|
'Content-Disposition' => 'inline; filename="'.$name.'"',
|
|
]);
|
|
}
|
|
}
|