E-Mail PDF-Downloads als Binärdaten stabilisieren

KI-AGENT: Attachment-Downloads werden im Backend explizit als Buffer mit Content-Length ausgeliefert und im Frontend aus einem ArrayBuffer als Blob erzeugt, damit PDF-Anhänge unverändert gespeichert werden.
This commit is contained in:
2026-05-23 20:52:22 +02:00
parent 8697810127
commit 4fd2eb9c40
3 changed files with 16 additions and 6 deletions

View File

@@ -668,7 +668,9 @@ export function emailSyncService(server: FastifyInstance) {
return { return {
filename: attachment.filename || row.attachment.filename || "anhang", filename: attachment.filename || row.attachment.filename || "anhang",
contentType: attachment.contentType || row.attachment.contentType || "application/octet-stream", contentType: attachment.contentType || row.attachment.contentType || "application/octet-stream",
content: attachment.content, content: Buffer.isBuffer(attachment.content)
? attachment.content
: Buffer.from(attachment.content),
} }
} }
} finally { } finally {

View File

@@ -466,9 +466,16 @@ export default async function emailAsUserRoutes(server: FastifyInstance) {
if (!attachment) return reply.code(404).send({ error: "Anhang nicht gefunden" }) if (!attachment) return reply.code(404).send({ error: "Anhang nicht gefunden" })
reply.header("Content-Type", attachment.contentType) const buffer = Buffer.isBuffer(attachment.content)
reply.header("Content-Disposition", `attachment; filename="${attachment.filename.replace(/"/g, "")}"`) ? attachment.content
return reply.send(attachment.content) : Buffer.from(attachment.content)
const filename = attachment.filename.replace(/["\r\n]/g, "")
reply.header("Content-Type", attachment.contentType || "application/octet-stream")
reply.header("Content-Length", buffer.length)
reply.header("Cache-Control", "no-store")
reply.header("Content-Disposition", `attachment; filename="${filename}"`)
return reply.send(buffer)
} catch (err: any) { } catch (err: any) {
req.log.error(err) req.log.error(err)
return reply.code(500).send({ error: err.message || "Anhang konnte nicht geladen werden" }) return reply.code(500).send({ error: err.message || "Anhang konnte nicht geladen werden" })

View File

@@ -457,13 +457,14 @@ async function downloadAttachment(attachment: NonNullable<EmailMessage["attachme
actionLoading.value = `attachment-${attachment.id}` actionLoading.value = `attachment-${attachment.id}`
try { try {
const response = await $fetch.raw(`/api/email/attachments/${attachment.id}/download`, { const response = await $fetch.raw(`/api/email/attachments/${attachment.id}/download`, {
responseType: "blob", responseType: "arrayBuffer",
credentials: "include", credentials: "include",
headers: { headers: {
...(useCookie("token").value ? { Authorization: `Bearer ${useCookie("token").value}` } : {}), ...(useCookie("token").value ? { Authorization: `Bearer ${useCookie("token").value}` } : {}),
}, },
}) })
const blob = response._data as Blob const contentType = response.headers.get("content-type") || attachment.contentType || "application/octet-stream"
const blob = new Blob([response._data as ArrayBuffer], { type: contentType })
const disposition = response.headers.get("content-disposition") || "" const disposition = response.headers.get("content-disposition") || ""
const dispositionFilename = disposition.match(/filename="([^"]+)"/)?.[1] const dispositionFilename = disposition.match(/filename="([^"]+)"/)?.[1]
const filename = dispositionFilename || attachment.filename || "anhang" const filename = dispositionFilename || attachment.filename || "anhang"