| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- <?php
- declare(strict_types=1);
- use App\App\Bootstrap;
- use App\Security\Csrf;
- use App\Security\FormAccess;
- use App\Storage\FileSystem;
- use App\Storage\JsonStore;
- require dirname(__DIR__) . '/src/autoload.php';
- Bootstrap::init();
- if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'message' => Bootstrap::appMessage('common.method_not_allowed'),
- ], 405);
- }
- $csrf = $_POST['csrf'] ?? '';
- if (!Csrf::validate(is_string($csrf) ? $csrf : null)) {
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'message' => Bootstrap::appMessage('common.invalid_csrf'),
- ], 419);
- }
- if (trim((string) ($_POST['website'] ?? '')) !== '') {
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'message' => Bootstrap::appMessage('common.request_blocked'),
- ], 400);
- }
- $email = strtolower(trim((string) ($_POST['email'] ?? '')));
- if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'message' => Bootstrap::appMessage('common.invalid_email'),
- ], 422);
- }
- $activityRaw = $_POST['last_user_activity_at'] ?? null;
- $lastUserActivityAt = is_scalar($activityRaw) ? (int) $activityRaw : null;
- $formAccess = new FormAccess();
- $auth = $formAccess->assertVerifiedForEmail($email, $lastUserActivityAt);
- if (($auth['ok'] ?? false) !== true) {
- $reason = (string) ($auth['reason'] ?? '');
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'message' => (string) ($auth['message'] ?? 'Bitte E-Mail erneut verifizieren.'),
- 'auth_required' => $reason === 'auth_required',
- 'auth_expired' => $reason === 'auth_expired',
- ], (int) ($auth['status_code'] ?? 401));
- }
- $field = trim((string) ($_POST['field'] ?? ''));
- $index = (int) ($_POST['index'] ?? -1);
- if ($field === '' || $index < 0) {
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'message' => Bootstrap::appMessage('delete_upload.invalid_upload_entry'),
- ], 422);
- }
- /** @return array{path: string, dir: string}|null */
- function resolveStoredUploadPath(array $entry, array $app): ?array
- {
- $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;
- $dir = dirname($path);
- $realBase = realpath($baseDir);
- if ($realBase !== false) {
- $realDir = realpath($dir);
- $realPath = realpath($path);
- if ($realDir !== false && !str_starts_with($realDir, $realBase)) {
- return null;
- }
- if ($realPath !== false && !str_starts_with($realPath, $realBase)) {
- return null;
- }
- }
- return ['path' => $path, 'dir' => $dir];
- }
- $app = Bootstrap::config('app');
- $store = new JsonStore();
- try {
- $result = $store->withEmailLock($email, static function () use ($store, $app, $email, $field, $index): array {
- if ($store->hasSubmission($email)) {
- return [
- 'ok' => false,
- 'status' => 409,
- 'message' => Bootstrap::appMessage('delete_upload.already_submitted'),
- ];
- }
- $draft = $store->getDraft($email);
- if (!is_array($draft)) {
- return [
- 'ok' => false,
- 'status' => 404,
- 'message' => Bootstrap::appMessage('delete_upload.draft_not_found'),
- ];
- }
- $uploads = (array) ($draft['uploads'] ?? []);
- $files = $uploads[$field] ?? null;
- if (!is_array($files) || !isset($files[$index]) || !is_array($files[$index])) {
- return [
- 'ok' => false,
- 'status' => 404,
- 'message' => Bootstrap::appMessage('delete_upload.upload_not_found'),
- ];
- }
- $entry = $files[$index];
- unset($files[$index]);
- $files = array_values($files);
- if ($files === []) {
- unset($uploads[$field]);
- } else {
- $uploads[$field] = $files;
- }
- $updatedDraft = $store->replaceDraft($email, [
- 'step' => $draft['step'] ?? 1,
- 'form_data' => (array) ($draft['form_data'] ?? []),
- 'uploads' => $uploads,
- ]);
- $resolved = resolveStoredUploadPath($entry, $app);
- if ($resolved !== null) {
- $fullPath = $resolved['path'];
- if (is_file($fullPath)) {
- @unlink($fullPath);
- }
- $entryDir = $resolved['dir'];
- if (is_dir($entryDir)) {
- $remaining = scandir($entryDir);
- if (is_array($remaining) && count($remaining) <= 2) {
- FileSystem::removeTree($entryDir);
- }
- }
- }
- return [
- 'ok' => true,
- 'status' => 200,
- 'uploads' => $updatedDraft['uploads'] ?? [],
- 'updated_at' => $updatedDraft['updated_at'] ?? null,
- ];
- });
- } catch (Throwable $e) {
- Bootstrap::log('app', 'delete-upload error: ' . $e->getMessage());
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'message' => Bootstrap::appMessage('delete_upload.delete_error'),
- ], 500);
- }
- if (($result['ok'] ?? false) !== true) {
- Bootstrap::jsonResponse([
- 'ok' => false,
- 'message' => (string) ($result['message'] ?? Bootstrap::appMessage('delete_upload.delete_error')),
- ], (int) ($result['status'] ?? 422));
- }
- Bootstrap::jsonResponse([
- 'ok' => true,
- 'message' => Bootstrap::appMessage('delete_upload.success'),
- 'uploads' => $result['uploads'] ?? [],
- 'updated_at' => $result['updated_at'] ?? null,
- ]);
|