mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-09-25 01:53:03 +02:00
- reliable tab title /favicon wrapper, - Content-Disposition filename, - admin media route hardened to thesis-file prefixes only (defense-in-depth)
75 lines
2.7 KiB
PHP
75 lines
2.7 KiB
PHP
<?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>
|