| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- <?php
- declare(strict_types=1);
- use App\App\Bootstrap;
- use App\Security\Csrf;
- use App\Security\RateLimiter;
- use App\Storage\JsonStore;
- require dirname(__DIR__) . '/src/autoload.php';
- Bootstrap::init();
- if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
- http_response_code(405);
- header('Content-Type: text/plain; charset=utf-8');
- echo 'Method not allowed';
- exit;
- }
- $csrf = $_GET['csrf'] ?? '';
- if (!Csrf::validate(is_string($csrf) ? $csrf : null)) {
- http_response_code(419);
- header('Content-Type: text/plain; charset=utf-8');
- echo 'Ungültiges CSRF-Token.';
- exit;
- }
- $email = strtolower(trim((string) ($_GET['email'] ?? '')));
- if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
- http_response_code(422);
- header('Content-Type: text/plain; charset=utf-8');
- echo 'Bitte gültige E-Mail eingeben.';
- exit;
- }
- $field = trim((string) ($_GET['field'] ?? ''));
- $index = (int) ($_GET['index'] ?? -1);
- if ($field === '' || $index < 0) {
- http_response_code(422);
- header('Content-Type: text/plain; charset=utf-8');
- echo 'Ungültiger Upload-Eintrag.';
- exit;
- }
- /** @return string|null */
- function resolveStoredPreviewPath(array $entry, array $app): ?string
- {
- $baseDir = rtrim((string) ($app['storage']['uploads'] ?? ''), '/');
- if ($baseDir === '') {
- return null;
- }
- $storedDir = trim((string) ($entry['stored_dir'] ?? ''), '/');
- $storedFilename = trim((string) ($entry['stored_filename'] ?? ''));
- if ($storedDir === '' || $storedFilename === '') {
- return null;
- }
- if (str_contains($storedDir, '..') || str_contains($storedFilename, '..')) {
- return null;
- }
- if (!preg_match('/^[A-Za-z0-9._\/-]+$/', $storedDir)) {
- return null;
- }
- if (!preg_match('/^[A-Za-z0-9._ -]+$/', $storedFilename)) {
- return null;
- }
- $path = $baseDir . '/' . $storedDir . '/' . $storedFilename;
- $realBase = realpath($baseDir);
- $realPath = realpath($path);
- if ($realBase !== false && $realPath !== false && !str_starts_with($realPath, $realBase)) {
- return null;
- }
- return $path;
- }
- $app = Bootstrap::config('app');
- $limiter = new RateLimiter();
- $ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
- $rateKey = sprintf('preview-upload:%s:%s', $ip, $email);
- if (!$limiter->allow($rateKey, (int) $app['rate_limit']['requests'], (int) $app['rate_limit']['window_seconds'])) {
- http_response_code(429);
- header('Content-Type: text/plain; charset=utf-8');
- echo 'Zu viele Anfragen. Bitte später erneut versuchen.';
- exit;
- }
- $store = new JsonStore();
- $draft = $store->getDraft($email);
- if (!is_array($draft)) {
- http_response_code(404);
- header('Content-Type: text/plain; charset=utf-8');
- echo 'Entwurf nicht gefunden.';
- exit;
- }
- $uploads = (array) ($draft['uploads'] ?? []);
- $files = $uploads[$field] ?? null;
- $entry = (is_array($files) && isset($files[$index]) && is_array($files[$index])) ? $files[$index] : null;
- if (!is_array($entry)) {
- http_response_code(404);
- header('Content-Type: text/plain; charset=utf-8');
- echo 'Upload nicht gefunden.';
- exit;
- }
- $path = resolveStoredPreviewPath($entry, $app);
- if ($path === null || !is_file($path)) {
- http_response_code(404);
- header('Content-Type: text/plain; charset=utf-8');
- echo 'Datei nicht gefunden.';
- exit;
- }
- $mime = (string) ($entry['mime'] ?? '');
- if ($mime === '') {
- $detected = @mime_content_type($path);
- $mime = is_string($detected) ? $detected : 'application/octet-stream';
- }
- $downloadName = (string) ($entry['original_filename'] ?? basename($path));
- $fallbackName = preg_replace('/[^A-Za-z0-9._-]/', '_', $downloadName) ?: 'upload.bin';
- $encodedName = rawurlencode($downloadName);
- header('Content-Type: ' . $mime);
- header('X-Content-Type-Options: nosniff');
- header('Content-Length: ' . (string) filesize($path));
- header('Content-Disposition: inline; filename="' . $fallbackName . '"; filename*=UTF-8\'\'' . $encodedName);
- readfile($path);
- exit;
|