Files
awo-hamburg-intranet/module/tasks/endpoints/admin/download_submission_file.php

100 lines
3.7 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
require_once dirname(dirname(__DIR__)) . '/bootstrap.php';
if (TaskCertRequest::method() !== 'GET') {
TaskCertResponse::error('Methode nicht erlaubt', 405, false);
}
$auth = TaskCertAuthContext::fromGlobals();
if (!$auth->isLoggedIn()) {
TaskCertResponse::error('Nicht eingeloggt', 401, false);
}
$csrfToken = TaskCertRequest::queryString('csrf_token', '');
if (!TaskCertCsrf::validate($csrfToken)) {
TaskCertResponse::error('CSRF validation failed', 419, false);
}
$submissionId = TaskCertRequest::queryInt('submission_id', 0);
$fieldKey = TaskCertRequest::queryString('field_key', '');
$index = TaskCertRequest::queryInt('index', -1);
if ($submissionId <= 0 || $fieldKey === '' || $index < 0) {
TaskCertResponse::error('Ungültige Parameter.', 400, false);
}
try {
$services = task_cert_services();
$submission = $services['submissionRepo']->findById($submissionId);
if ($submission === null) {
TaskCertResponse::error('Einreichung nicht gefunden.', 404, false);
}
// Zugriff: Admin ODER Eigentümer der Einreichung.
$isAdmin = $auth->hasPermission('r-task-cert-admin');
$isOwner = (int) ($submission['main_contact_id'] ?? 0) === $auth->contactId();
if (!$isAdmin && !$isOwner) {
TaskCertResponse::error('Keine Berechtigung', 403, false);
}
$payload = is_array($submission['payload'] ?? null) ? $submission['payload'] : [];
$formValues = is_array($payload['form_values'] ?? null) ? $payload['form_values'] : [];
$fieldFiles = is_array($formValues[$fieldKey] ?? null) ? $formValues[$fieldKey] : [];
$fileMeta = is_array($fieldFiles[$index] ?? null) ? $fieldFiles[$index] : null;
if ($fileMeta === null || !isset($fileMeta['file_path'])) {
TaskCertResponse::error('Datei nicht gefunden.', 404, false);
}
$absolutePath = TaskUploadStorage::absolutePath((string) $fileMeta['file_path']);
if ($absolutePath === null || !is_file($absolutePath)) {
TaskCertResponse::error('Datei nicht gefunden.', 404, false);
}
$fileName = (string) ($fileMeta['file_name'] ?? 'datei');
// Content-Type IMMER aus der (erlaubten) Endung ableiten — nie aus dem
// gespeicherten/Client-MIME (schützt auch Alt-Datensätze vor Stored-XSS,
// z. B. als Bild getarntes SVG).
$mime = TaskUploadPolicy::mimeForFilename($fileName);
// RFC-5987: ASCII-Fallback für alte Clients + UTF-8-URL-encoded für moderne.
$asciiFallback = preg_replace('/[^\x20-\x7E]+/', '_', $fileName) ?: 'datei';
// Inline nur für eine feste, sichere MIME-Menge (kein SVG/HTML/XML),
// alles andere als Download.
$disposition = TaskUploadPolicy::isInlineSafeMime($mime) ? 'inline' : 'attachment';
if (!headers_sent()) {
http_response_code(200);
header('Content-Type: ' . $mime);
header(sprintf(
'Content-Disposition: %s; filename="%s"; filename*=UTF-8\'\'%s',
$disposition,
addcslashes($asciiFallback, '"\\'),
rawurlencode($fileName)
));
header('Content-Length: ' . (int) filesize($absolutePath));
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: SAMEORIGIN');
header('Cache-Control: private, max-age=0, must-revalidate');
}
while (ob_get_level() > 0) {
ob_end_clean();
}
readfile($absolutePath);
exit;
} catch (Throwable $throwable) {
TaskCertLogger::error('Submission-Datei-Download fehlgeschlagen', [
'submission_id' => $submissionId,
'field_key' => $fieldKey,
'index' => $index,
'exception' => $throwable,
]);
TaskCertResponse::error('Download fehlgeschlagen.', 500, false);
}