Files
maria_uslugi/messages/api/download_attachment.php
2026-03-05 10:25:39 +01:00

114 lines
3.5 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// /messages/api/download_attachment.php
declare(strict_types=1);
require_once __DIR__ . '/../_bootstrap.php';
// Important: aucune sortie HTML avant les headers !
/**
* Règles d’accès:
* - On reçoit ?message_id=ID (GET).
* - Vérifie que l’utilisateur courant participe à la conversation du message.
* - Vérifie que le message n’est pas rétracté et pas "supprimé pour moi".
* - Sert le fichier avec le bon Content-Type et Content-Disposition.
*/
$messageId = (int)($_GET['message_id'] ?? 0);
if ($messageId <= 0) {
http_response_code(400);
echo "Bad params";
exit;
}
$role = $GLOBALS['MESSAGES_CTX']['role']; // 'praticien'|'client' (en CLI, inutilisé)
$meId = $GLOBALS['MESSAGES_CTX']['me_id'];
$hideCol = ($role === 'praticien') ? 'deleted_for_praticien' : 'deleted_for_client';
// Récupère message + conversation
$sql = "SELECT m.id, m.conversation_id, m.attachment_path, m.attachment_name,
m.retracted_for_all, m.$hideCol AS hidden_for_me,
c.praticien_id, c.client_id
FROM messages m
JOIN conversations c ON c.id = m.conversation_id
WHERE m.id = ?
LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([$messageId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
http_response_code(404);
echo "Message not found";
exit;
}
// Accès: dois être participant de la conversation
assert_conversation_access($pdo, (int)$row['conversation_id'], $role, (int)$meId);
// Visibilité: pas rétracté, pas supprimé pour moi, et PJ existante
if (!empty($row['retracted_for_all']) || !empty($row['hidden_for_me'])) {
http_response_code(403);
echo "Forbidden";
exit;
}
$webRel = (string)($row['attachment_path'] ?? '');
if ($webRel === '') {
http_response_code(404);
echo "No attachment";
exit;
}
// Sécurité chemin: doit commencer par messages/uploads/
if (strpos($webRel, 'messages/uploads/') !== 0) {
http_response_code(400);
echo "Invalid path";
exit;
}
// Résolution chemin disque
$siteRoot = realpath(__DIR__ . '/../../'); // racine site
$uploadsAbs = $siteRoot . DIRECTORY_SEPARATOR . str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $webRel);
$real = realpath($uploadsAbs);
$uploadsRoot = realpath(__DIR__ . '/../uploads/');
if (!$real || !$uploadsRoot || strpos($real, $uploadsRoot) !== 0 || !is_file($real)) {
http_response_code(404);
echo "File not found";
exit;
}
// Détection MIME + nom fichier
$finfo = function_exists('finfo_open') ? finfo_open(FILEINFO_MIME_TYPE) : false;
$mime = ($finfo && is_file($real)) ? finfo_file($finfo, $real) : null;
if ($finfo) { @finfo_close($finfo); }
if (!$mime) $mime = 'application/octet-stream';
$name = (string)($row['attachment_name'] ?? basename($real));
$size = filesize($real);
// En-têtes (évite le sniffing, force téléchargement)
header('Content-Type: ' . $mime);
header('Content-Length: ' . (string)$size);
header('X-Content-Type-Options: nosniff');
header('Content-Security-Policy: default-src \'none\'; frame-ancestors \'none\'; sandbox');
header('Content-Disposition: attachment; filename*=UTF-8\'\'' . rawurlencode($name));
// Lecture par chunks (robuste)
$fp = @fopen($real, 'rb');
if (!$fp) {
http_response_code(500);
echo "Cannot open file";
exit;
}
while (!feof($fp)) {
$buf = fread($fp, 8192);
if ($buf === false) break;
echo $buf;
// facultatif: flush pour gros fichiers
if (function_exists('fastcgi_finish_request')) { /* noop here */ }
}
fclose($fp);
exit;