getFileVisibility($requestedPath); if ($accessTypeId !== null && $accessTypeId === 3) { // 3 = Interdit — block entirely http_response_code(403); exit; } // 2 = Interne — allow (no session auth requirement for now; could add later) } catch (\Throwable $e) { // On DB error, fail open (don't block legitimate requests) error_log("media.php visibility check error: " . $e->getMessage()); } } // --- 3. Verify MIME type from file content (not extension) -------------------- $finfo = new finfo(FILEINFO_MIME_TYPE); $mimeType = $finfo->file($realFull); $allowedMimes = [ 'image/jpeg', 'image/png', 'image/gif', 'application/pdf', 'video/mp4', 'application/zip', 'text/vtt', // WebVTT caption sidecar files ]; // finfo may return 'text/plain' for WebVTT files on some systems; // re-classify by extension so we don't block them. if ($mimeType === 'text/plain' && strtolower(pathinfo($realFull, PATHINFO_EXTENSION)) === 'vtt') { $mimeType = 'text/vtt'; } if (!in_array($mimeType, $allowedMimes, true)) { http_response_code(403); exit; } // --- 4. Send response headers ------------------------------------------------- header('Content-Type: ' . $mimeType); header('Content-Length: ' . filesize($realFull)); header('X-Content-Type-Options: nosniff'); $ext = strtolower(pathinfo($realFull, PATHINFO_EXTENSION)); if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif'], true)) { // Images: cache publicly for 7 days header('Cache-Control: public, max-age=604800'); } elseif ($ext === 'pdf') { // PDFs: cache for 1 day, display inline header('Cache-Control: public, max-age=86400'); header('Content-Disposition: inline'); } elseif ($ext === 'vtt') { // WebVTT captions: serve as text/vtt, cache 1 day header('Content-Type: text/vtt; charset=utf-8'); header('Cache-Control: public, max-age=86400'); } else { // Everything else: no public caching header('Cache-Control: private, no-store'); } // --- 5. Stream file ----------------------------------------------------------- readfile($realFull);