Checkpoint: Correction du bouton téléchargement BAP : la route /api/download-bap utilise maintenant un query param pdfPath pour accepter les chemins complets avec sous-dossiers (ex: /storage/2026-04/BAP_xxx.pdf). Ajout d'une route de compatibilité qui cherche dans tous les sous-dossiers. Sécurité anti-path-traversal maintenue.

This commit is contained in:
Manus
2026-04-12 10:31:18 -04:00
parent 9d71f1e34a
commit d4fe15c23d
3 changed files with 49 additions and 6 deletions

View File

@@ -41,18 +41,57 @@ async function startServer() {
app.use("/storage", express.static("storage"));
// Route de téléchargement forcé du PDF annoté BAP
app.get("/api/download-bap/:filename", (req, res) => {
const filename = path.basename(req.params.filename); // sécurité : pas de path traversal
const storagePath = path.resolve("storage", filename);
// Accepte les chemins avec sous-dossiers : /api/download-bap/2026-04/filename.pdf
// ou via query param pdfPath : /api/download-bap/file.pdf?pdfPath=/storage/2026-04/file.pdf
app.get("/api/download-bap", (req, res) => {
// Mode 1 : query param pdfPath (chemin complet depuis /storage/...)
const pdfPath = req.query.pdfPath as string | undefined;
if (!pdfPath) {
res.status(400).json({ error: "Paramètre pdfPath manquant" });
return;
}
// Sécurité : s'assurer que le chemin est bien dans le dossier storage
const normalized = path.normalize(pdfPath).replace(/^\/+/, '');
if (normalized.startsWith('..') || !normalized.startsWith('storage')) {
res.status(403).json({ error: "Accès refusé" });
return;
}
const storagePath = path.resolve(normalized);
if (!fs.existsSync(storagePath)) {
res.status(404).json({ error: "Fichier introuvable" });
return;
}
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
const filename = path.basename(storagePath);
res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(filename)}"`);
res.setHeader("Content-Type", "application/pdf");
res.sendFile(storagePath);
});
// Compat. ancienne route avec :filename (sans sous-dossier)
app.get("/api/download-bap/:filename", (req, res) => {
const filename = path.basename(req.params.filename);
// Chercher dans tous les sous-dossiers de storage
const storageRoot = path.resolve("storage");
let found: string | null = null;
try {
const subdirs = fs.readdirSync(storageRoot);
for (const sub of subdirs) {
const candidate = path.join(storageRoot, sub, filename);
if (fs.existsSync(candidate)) { found = candidate; break; }
}
// Aussi essayer directement dans storage/
const direct = path.join(storageRoot, filename);
if (!found && fs.existsSync(direct)) found = direct;
} catch { /* ignore */ }
if (!found) {
res.status(404).json({ error: "Fichier introuvable" });
return;
}
res.setHeader("Content-Disposition", `attachment; filename="${encodeURIComponent(filename)}"`);
res.setHeader("Content-Type", "application/pdf");
res.sendFile(found);
});
// tRPC API
app.use(
"/api/trpc",