mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
fix: pass PDF bytes to pdf.js instead of fetching the blob: URL, which CSP connect-src blocks #871
This commit is contained in:
@@ -155,6 +155,11 @@ export function FilePreviewModal({ name, onClose, onDownload, getFileContent }:
|
||||
// (text/html, image/svg+xml, ...) in a new tab would execute it in-origin.
|
||||
const [canOpenInNewTab, setCanOpenInNewTab] = useState(false);
|
||||
const [emlContent, setEmlContent] = useState<ParsedEml | null>(null);
|
||||
// The PDF blob itself, kept alongside its object URL: the pdf.js mobile
|
||||
// viewer needs the bytes, because fetching the blob: URL is blocked by CSP
|
||||
// connect-src (#871). The URL is still what the <iframe> and the
|
||||
// open-in-new-tab fallback use.
|
||||
const [pdfBlob, setPdfBlob] = useState<Blob | null>(null);
|
||||
|
||||
// Decide whether to render the PDF in a plain <iframe> (desktop) or with the
|
||||
// pdf.js canvas viewer (mobile). navigator.pdfViewerEnabled is the standard
|
||||
@@ -208,6 +213,7 @@ export function FilePreviewModal({ name, onClose, onDownload, getFileContent }:
|
||||
setObjectUrl(null);
|
||||
setCanOpenInNewTab(false);
|
||||
setEmlContent(null);
|
||||
setPdfBlob(null);
|
||||
setLoading(true);
|
||||
setError(false);
|
||||
setResolvedFileType(getFilePreviewKind(name));
|
||||
@@ -253,6 +259,7 @@ export function FilePreviewModal({ name, onClose, onDownload, getFileContent }:
|
||||
if (!cancelled) {
|
||||
setObjectUrl(revokeUrl);
|
||||
setCanOpenInNewTab(isMimeTypeSafeForInlinePreview(effectiveType));
|
||||
if (previewType === "pdf") setPdfBlob(typedBlob);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
@@ -374,7 +381,7 @@ export function FilePreviewModal({ name, onClose, onDownload, getFileContent }:
|
||||
/>
|
||||
)}
|
||||
|
||||
{!loading && !error && fileType === "pdf" && objectUrl && !pdfInlineSupported && (
|
||||
{!loading && !error && fileType === "pdf" && objectUrl && pdfBlob && !pdfInlineSupported && (
|
||||
// Mobile browsers can't show a PDF inline in an <iframe> (Android: a
|
||||
// blank frame / silent download; iOS: only the first page), so render
|
||||
// it with pdf.js (canvas) instead. The header's open-in-new-tab /
|
||||
@@ -383,7 +390,7 @@ export function FilePreviewModal({ name, onClose, onDownload, getFileContent }:
|
||||
className="w-full max-w-3xl h-full overflow-auto rounded-lg bg-neutral-200 dark:bg-neutral-800 p-2"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<PdfMobileViewer url={objectUrl} />
|
||||
<PdfMobileViewer url={objectUrl} blob={pdfBlob} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -27,7 +27,11 @@ const MAX_ZOOM = 4; // pinch can go a bit beyond the double-tap steps
|
||||
const DOUBLE_TAP_MS = 300;
|
||||
const DOUBLE_TAP_SLOP = 30; // px
|
||||
|
||||
export function PdfMobileViewer({ url }: { url: string }) {
|
||||
// `blob` is the document itself; `url` (a blob: URL for the same bytes) is only
|
||||
// used for the open-in-new-tab fallback. pdf.js must be fed the bytes, not the
|
||||
// URL: fetching a blob: URL is governed by CSP connect-src, which doesn't (and
|
||||
// shouldn't need to) allow blob:, so getDocument({ url }) is blocked (#871).
|
||||
export function PdfMobileViewer({ url, blob }: { url: string; blob: Blob }) {
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const pagesRef = useRef<HTMLDivElement>(null);
|
||||
const zoom = useRef({ step: 0, scale: 1 });
|
||||
@@ -51,7 +55,12 @@ export function PdfMobileViewer({ url }: { url: string }) {
|
||||
import.meta.url,
|
||||
).toString();
|
||||
|
||||
loadingTask = pdfjs.getDocument({ url });
|
||||
// The Uint8Array is created fresh from the blob, so pdf.js taking
|
||||
// ownership of (and transferring) the buffer is fine.
|
||||
const data = new Uint8Array(await blob.arrayBuffer());
|
||||
if (cancelled) return;
|
||||
|
||||
loadingTask = pdfjs.getDocument({ data });
|
||||
const doc = await loadingTask.promise;
|
||||
if (cancelled) return;
|
||||
|
||||
@@ -92,7 +101,7 @@ export function PdfMobileViewer({ url }: { url: string }) {
|
||||
cancelled = true;
|
||||
void loadingTask?.destroy().catch(() => {});
|
||||
};
|
||||
}, [url]);
|
||||
}, [blob]);
|
||||
|
||||
// Gesture zoom. 1-finger double-tap cycles the steps (fit -> 2x -> 3x ->
|
||||
// fit); 2-finger pinch zooms continuously up to MAX_ZOOM. Both drive a
|
||||
|
||||
Reference in New Issue
Block a user