// Bootstrap for the standalone PDF viewer page. Plays the role of app.js in // viewer-phase1: it owns the #pdfMessage overlay and hands one URL to PdfViewer // (from pdf_viewer.js, unchanged). Everything else — zoom, resize, retry — lives // in PdfViewer already. // // URL contract: /viewer/index.html?file= // Same-origin only: any file value that is not a root-relative /api/ path is // refused before it ever reaches pdf.js. const viewer = document.querySelector(".viewer"); const pdfContainer = document.querySelector("#pdfViewer"); const pdfMessage = document.querySelector("#pdfMessage"); function showMessage(text) { viewer.classList.remove("loaded"); pdfMessage.textContent = text; pdfMessage.hidden = false; } // Only root-relative /api/ paths. Rejects absolute URLs ("https://..."), // protocol-relative ones ("//host/api/x") and anything outside /api/. function safeFileUrl(raw) { if (!raw) return null; if (!raw.startsWith("/api/") || raw.startsWith("//")) return null; // A backslash can be normalized to "/" by some URL parsers — reject outright. if (raw.includes("\\")) return null; return raw; } const params = new URLSearchParams(globalThis.location.search); const file = safeFileUrl(params.get("file")); if (!file) { showMessage( params.get("file") ? "Refused to load this document: only same-origin /api/ paths are allowed." : "Select a document", ); } else { showMessage("Loading PDF…"); const pdfViewer = new PdfViewer(pdfContainer); viewer.classList.add("loaded"); pdfMessage.hidden = true; pdfViewer.load(file).catch((error) => { showMessage( `Cannot open PDF: ${ error instanceof Error ? error.message : String(error) }`, ); }); }