<?php

/**
 * Konfiguration und Härtung der Dateiablage /dateien (Files Gallery).
 *
 * DIE EINZIGE STELLE, an der die Dateiablage konfiguriert wird. index.php bleibt
 * unangetastet (bis auf den TSV-PATCH in Zeile ~122), und
 * storage/dateien/system/config/config.php wird von der App selbst generiert und bei
 * jedem Update überschrieben (index.php:248) — dort niemals etwas eintragen.
 * Diese Datei lädt index.php als erstes (index.php:142) und sie gewinnt gegen beide
 * anderen Ebenen (index.php:239), überlebt also jedes Update.
 *
 * Sie läuft VOR session_start() (index.php:295) und VOR dem Login (index.php:159) —
 * deshalb dürfen Session-Härtung und Login-Bremse hier stehen; später wäre es zu spät.
 *
 * Secrets (Lizenzschlüssel, Passwort-Hashes) stehen in config/config.php, nicht hier.
 */

require_once dirname(__DIR__, 2) . '/app/bootstrap.php';

/*
 * Die App ist gegen PHP 7 geschrieben und wirft unter 8.5 Deprecation-Notices
 * (z. B. imagedestroy()). Mit display_errors — also lokal bei env: development — landen
 * die MITTEN im Bild-Stream und machen jedes Thumbnail kaputt. Deshalb hier immer aus:
 * Ausgaben dieser App sind zum großen Teil Binärdaten, für die es keinen sinnvollen Ort
 * für Fehlertexte gibt. Echte Fehler stehen weiter in storage/logs/php-errors.log.
 */
ini_set('display_errors', '0');
error_reporting(E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED);

$base = ROOT_PATH . '/storage/dateien';

/*
 * Das Vereins-Konto MUSS das globale username/password der App sein, nicht bloß ein
 * Konto unter system/users/. Sonst gilt index.php:328-337: ohne globales Passwort sind
 * die Konten aus dem users-Verzeichnis nur ZUSÄTZLICHE Logins, und der Grundzustand
 * bleibt eine offen lesbare Ablage. Genau das ist beim Bauen einmal passiert.
 * Der Admin liegt daneben in system/users/admin/config.php.
 *
 * Fail closed: fehlt der Hash in config/config.php, tritt ein Zufallswert an seine
 * Stelle. Dann verlangt die App weiter ein Passwort, das niemand kennt — statt die
 * Ablage aufzumachen, weil das Passwort leer ist.
 */
$vereinPassword = (string) config('dateien.users.verein.password_hash', '');
if ($vereinPassword === '') {
    $vereinPassword = bin2hex(random_bytes(32));
}

/*
 * Session-Cookie härten. Files Gallery lässt die PHP-Defaults stehen (index.php:291
 * und 298 sind auskommentiert): Logout beim Schließen des Browsers und kein
 * SameSite/Secure-Flag. Für einen Upload vom Handy ist das zu kurz gedacht.
 */
if (session_status() === PHP_SESSION_NONE) {
    $https = ($_SERVER['HTTPS'] ?? '') === 'on'
        || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? '') === 'https';

    session_set_cookie_params([
        'lifetime' => 28800, // 8 h — ein Upload-Nachmittag ohne erneutes Anmelden
        // Ohne Slash am Ende — Absicht: ein Cookie mit `path=/dateien/` wird bei einem
        // Aufruf von `/dateien` (ohne Slash) NICHT mitgesendet (RFC 6265 §5.1.4). Dann
        // fehlt das CSRF-Token in der Session und jeder Login scheitert als „ungültig".
        // Apache leitet `/dateien` zwar auf `/dateien/` um, der Dev-Server aber nicht.
        // `/dateien` deckt beide Formen ab und greift nicht auf `/datenschutz` über.
        'path'     => '/dateien',
        'secure'   => $https, // lokal (http) sonst kein Cookie und damit kein Login
        'httponly' => true,
        'samesite' => 'Lax', // trägt zusätzlich das fehlende CSRF-Handling der App (index.php:301)
    ]);
    ini_set('session.gc_maxlifetime', '28800');
}

/*
 * Brute-Force-Bremse. Files Gallery bringt keine mit — im gesamten index.php gibt es
 * kein Rate-Limit, Anmeldeversuche sind unbegrenzt. Zähler und Log sind dieselben wie
 * bei den Formularen (storage/ratelimit/, storage/logs/spam.log).
 * Der Login-POST kommt aus der App per fetch() als JSON-Body (index.php:384), aus dem
 * Login-Formular als normales POST — daher beide Wege prüfen.
 */
$isLoginAttempt = ($_SERVER['REQUEST_METHOD'] ?? '') === 'POST'
    && (($_GET['action'] ?? '') === 'login' || isset($_POST['fusername']));

if ($isLoginAttempt && !rate_limit_ok('dateien-login:' . client_ip(), 10, 600)) {
    log_spam('dateien', 'login-ratelimit');
    http_response_code(429);
    header('Retry-After: 600');
    header('Content-Type: text/plain; charset=utf-8');
    exit("Zu viele Anmeldeversuche. Bitte in 10 Minuten noch einmal probieren.\n");
}

/*
 * SVG beim Upload auf ausführbare Bestandteile prüfen — die einzige erlaubte Endung, die
 * Code tragen kann. Die CSP `sandbox` in public/dateien/.htaccess entschärft das im
 * Browser, ist aber die zweite Reihe: hier landet die Datei gar nicht erst in der Ablage.
 * Ein Vereins- oder Sponsorenlogo braucht kein <script>, kein onload und keine externen
 * Verweise; Vektorgrafik bleibt Vektorgrafik.
 *
 * Muss hier stehen, weil diese Datei als erstes geladen wird: $_FILES ist von PHP schon
 * gefüllt, die App hat die Datei aber noch nicht verschoben (index.php:4116).
 */
$upload = $_FILES['file'] ?? null;
if (($_GET['action'] ?? '') === 'upload'
    && is_array($upload)
    && is_string($upload['name'] ?? null)
    && is_string($upload['tmp_name'] ?? null)
    && preg_match('/\.svgz?$/i', $upload['name']) === 1
    && is_uploaded_file($upload['tmp_name'])
) {
    $reject = static function (string $message): never {
        log_spam('dateien', 'svg-abgewiesen');
        header('Content-Type: application/json');
        echo json_encode(['error' => $message], JSON_UNESCAPED_UNICODE);
        exit;
    };

    // Eine Vektorgrafik dieser Größe ist keine: nicht einlesen, sondern abweisen
    // (sonst müsste der Scan Hunderte MB in den Speicher holen).
    if ((int) ($upload['size'] ?? 0) > 4194304) {
        $reject('Diese SVG ist größer als 4 MB und wurde abgewiesen. Bitte als PNG oder JPG hochladen.');
    }

    $svg = (string) file_get_contents($upload['tmp_name']);

    // Skript und Nachladen von außen. Der href/src-Teil trifft auch <image> und <use> und
    // hält damit Regel 1 ein: eine SVG darf keinen fremden Host kontaktieren. Interne
    // Verweise (href="#gradient") und eingebettete Daten (href="data:…") bleiben erlaubt,
    // ebenso die xmlns-Deklarationen, die naturgemäß http-URLs enthalten.
    $gefaehrlich = '/<script|javascript:|\son[a-z]+\s*=|<foreignObject|<!ENTITY'
        . '|(?:xlink:)?(?:href|src)\s*=\s*["\']?\s*(?:https?:|\/\/)/i';

    if (preg_match($gefaehrlich, $svg) === 1) {
        $reject('Diese SVG enthält Skript oder verweist auf fremde Server und wurde abgewiesen. Bitte ohne Interaktivität und ohne verlinkte Bilder exportieren — oder als PNG hochladen.');
    }
}

return [
    /*
     * Die Dateien der Nutzer liegen AUSSERHALB des Docroots. Damit sind sie per URL nicht
     * erreichbar, und die App liefert sie selbst durch PHP aus (index.php:1607) — eine
     * hochgeladene .php kann so prinzipiell nicht ausgeführt werden. Das ist die wichtigste
     * Schutzschicht, weil die App per Default JEDE Endung zum Upload zulässt.
     */
    'root'         => $base . '/uploads',
    'storage_path' => $base . '/system', // Cache, Nutzer, generierte Config — ebenfalls außerhalb
    'root_lock'    => $base, // kein Per-User-root kann hier ausbrechen (index.php:1146)

    /*
     * Auslieferung immer durch PHP erzwingen. Nötig für den Fall, dass der Hoster das
     * Repo-Root statt public/ serviert: dann läge storage/ innerhalb des DOCUMENT_ROOT,
     * die App würde direkte URLs bauen — und die Root-.htaccess blockt /storage/ mit 403.
     */
    'load_files_proxy_php' => true,

    // Assets self-hosted (harte Regel 1: keine CDNs). Bestand prüfen/erneuern:
    // php bin/filesgallery-update.php --check
    'assets' => '/assets/filesgallery',

    'license_key' => (string) config('dateien.license_key', ''),

    /*
     * Zugang „verein" — das gemeinsame Konto zum Hochladen (Passwort im Verein
     * weitergeben). Weil es das globale Konto ist, sind die folgenden Rechte gleichzeitig
     * die Grundrechte der Ablage; der Admin hebt sie in system/users/admin/config.php an.
     *
     * Bewusst KEIN Löschen, Umbenennen oder Verschieben: an einem gemeinsamen Konto darf
     * ein Fehlklick nicht die Fotos anderer treffen. Aufräumen macht der Admin.
     */
    'username'         => 'verein',
    'password'         => $vereinPassword,
    'allow_upload'     => true,
    'allow_new_folder' => true,
    'allow_download'   => true,

    // Deutsche Oberfläche, Browsersprache darf weiterhin gewinnen.
    'lang_default' => 'de',

    // Punkt-Dateien ausblenden (Regex, nicht fnmatch): .gitkeep hält das Upload-Verzeichnis
    // im Repo, hat in der Ablage aber nichts zu suchen — ebenso .DS_Store vom Mac.
    'files_exclude' => '/^\./',

    /*
     * Upload-Filter. Default ist ein LEERER String und bedeutet "alles erlaubt" — es gibt
     * keine Blacklist (index.php:4078-4092). Die Liste ist also nicht Kosmetik, sondern
     * die serverseitige Grenze. .heic/.heif, weil iPhones so fotografieren.
     */
    /*
     * .svg ist bewusst dabei (Sponsoren- und Vereinslogos kommen als Vektor), aber es
     * ist der einzige Eintrag in dieser Liste, der ausführbaren Code enthalten kann:
     * eine SVG darf <script> tragen, und die App liefert Dateien inline aus
     * (index.php:781). Dagegen setzt public/dateien/.htaccess für SVG-Antworten
     * `Content-Security-Policy: sandbox` — die Datei wird dann als Dokument ohne
     * Skripte und ohne Origin-Zugehörigkeit geladen. Wer die Zeile hier ändert, muss
     * die dort mitziehen.
     */
    'upload_allowed_file_types' => '.jpg, .jpeg, .png, .webp, .gif, .heic, .heif, .svg, .mp4, .mov, .m4v, .webm, .pdf',

    /*
     * 250 MB pro Datei. Das ist auf Videoclips gerechnet: eine Minute Full-HD vom Handy
     * sind rund 130 MB, 4K etwa das Dreifache — mit 100 MB wäre schon ein Clip von
     * 45 Sekunden abgewiesen worden. Die PHP-Grenzen müssen mitziehen, sonst schneidet
     * PHP vorher ab: public/dateien/.user.ini (Server) bzw. die -d-Schalter im
     * Dev-Kommando. Es gilt immer die kleinere der beiden Grenzen.
     */
    'upload_max_filesize'       => 262144000, // Bytes = 250 MB
    'upload_exists'             => 'increment', // zwei "IMG_1234.jpg" überschreiben sich nie

    /*
     * Alles abschalten, was der Verein nicht braucht und was die Angriffsfläche vergrößert.
     * Rechte pro Konto stehen in storage/dateien/system/users/<name>/config.php.
     */
    'allow_settings'      => false, // das Settings-UI schreibt und includet geliefertes PHP (index.php:1019-1062)
    'allow_check_updates' => false, // würde index.php ungeprüft von jsDelivr überschreiben (index.php:3956)
    'allow_tests'         => false, // Default ist TRUE und leakt Server-Infos + Config (nur admin darf es)
    'allow_tasks'         => false,
    'allow_symlinks'      => false, // kein Ausbruch aus dem Upload-Verzeichnis über Symlinks
    'index_cache'         => false, // würde eine statische index.html in public/dateien/ ablegen
];
