Add admin-only route to open Interdit thesis files from backoffice:

- reliable tab title /favicon wrapper,
- Content-Disposition filename,
- admin media route hardened to thesis-file prefixes only (defense-in-depth)
This commit is contained in:
Pontoporeia
2026-09-18 16:26:36 +02:00
parent e518163c5b
commit 6e1fc6a781
9 changed files with 404 additions and 7 deletions
+74
View File
@@ -0,0 +1,74 @@
<?php
/**
* Admin media viewer — a minimal HTML wrapper that reliably sets a useful
* browser tab title when opening thesis files (incl. 'Interdit') from the
* backoffice.
*
* Why a wrapper?
* - PDFs served directly show the URL ("media.php") as the tab title because
* most don't embed a /Title metadata and Chrome/Firefox's PDF viewer ignores
* Content-Disposition: filename for the tab. A wrapper <title> always wins.
* - The actual bytes stream through /admin/media.php (the server validated
* inlined below), preserving the existing security checks (path whitelist,
* realpath() jail, MIME allow-list, Interdit bypass gated by admin session).
*
* Security:
* - Auth-gated by AdminAuth::requireLogin().
* - Path is validated against thesis-file prefixes before it is passed on.
* - The embedded iframe is same-origin (/admin/media.php); admin CSP permits
* frame-src 'self'. noindex/nofollow and no-referrer keep it private.
*
* Usage: /admin/media-viewer.php?path=tfe/2025/2025-001/rapport.pdf
*/
require_once __DIR__ . '/../../bootstrap.php';
require_once __DIR__ . '/../../src/AdminAuth.php';
AdminAuth::requireLogin();
require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/ErrorHandler.php';
$path = trim((string)($_GET['path'] ?? ''));
// Only thesis-file paths are ever served; reject anything else early.
if (!preg_match('#^(theses|documents|tfe|these|frart)/[^?&]{1,255}$#', $path)) {
http_response_code(400);
echo 'Chemin invalide.';
exit;
}
$displayName = 'Document';
try {
$mediaDb = Database::getInstance();
$displayName = $mediaDb->getFileDisplayName($path) ?? basename($path);
} catch (\Throwable $e) {
ErrorHandler::log('media_viewer_display', $e, ['path' => $path]);
}
if ($displayName === '') {
$displayName = 'Document';
}
$src = '/admin/media.php?path=' . urlencode($path);
$title = htmlspecialchars($displayName);
$srcAttr = htmlspecialchars($src);
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<meta name="referrer" content="no-referrer">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon/favicon-16x16.png">
<link rel="shortcut icon" href="/assets/favicon/favicon.ico">
<title><?= $title ?> – XAMXAM</title>
<style>
html, body { margin: 0; height: 100%; background: #525659; }
iframe { display: block; width: 100%; height: 100%; border: 0; background: #525659; }
</style>
</head>
<body>
<iframe src="<?= $srcAttr ?>" title="<?= $title ?>" allow="autoplay; fullscreen"></iframe>
</body>
</html>