Init Git Prod Mars 2026

This commit is contained in:
Oleg Barykin committed 2026-03-05 10:25:39 +01:00
commit d81efe93b1
362 files changed
+86627

No files matched your search

+113
View File
@@ -0,0 +1,113 @@
<?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;