diff --git a/server/api/membership/generate-pdf.post.js b/server/api/membership/generate-pdf.post.js index 4aebfb9..6bed0cd 100644 --- a/server/api/membership/generate-pdf.post.js +++ b/server/api/membership/generate-pdf.post.js @@ -3,7 +3,7 @@ import { exec } from 'child_process' import { promisify } from 'util' import fs from 'fs/promises' import path from 'path' -import { StandardFonts } from 'pdf-lib' +import { PDFDocument, StandardFonts, rgb } from 'pdf-lib' import { getDownloadCookieOptionsWithMaxAge } from '../../utils/cookies.js' import { sendMembershipEmail as sendMembershipEmailUtil } from '../../utils/email-service.js' import { getProjectPath, getServerDataPath } from '../../utils/paths.js' @@ -100,7 +100,7 @@ function generateLaTeXContent(data) { // LaTeX-Inhalt mit korrekten Escapes generieren let latexContent = `\\documentclass[12pt,a4paper]{article} \\usepackage[utf8]{inputenc} -\\usepackage[ngerman]{babel} +\\usepackage[german]{babel} \\usepackage{geometry} \\usepackage{enumitem} \\usepackage{xcolor} @@ -368,8 +368,8 @@ export default defineEventHandler(async (event) => { if (!res.ok) throw new Error(`Template konnte nicht geladen werden: ${res.status}`) arrayBuffer = await res.arrayBuffer() } - } catch (_e) { - throw new Error('Template-Laden fehlgeschlagen: ' + e.message) + } catch (templateLoadError) { + throw new Error('Template-Laden fehlgeschlagen: ' + templateLoadError.message) } const pdfDoc = await PDFDocument.load(arrayBuffer) @@ -385,7 +385,7 @@ export default defineEventHandler(async (event) => { // Koordinaten (in PDF-Punkten) müssen ggf. feinjustiert werden. const pages = pdfDoc.getPages() const firstPage = pages[0] - firstPage.getSize() + const { height } = firstPage.getSize() // Schätzwerte: (x, y) in Punkten von linker unteren Ecke // Diese Werte müssen nach Sichtprüfung justiert werden. @@ -423,22 +423,8 @@ export default defineEventHandler(async (event) => { bank: { x: leftX, y: baseY - gap * 3 + yOffset } } - const drawText = (page, text, x, y, size = 11) => { - page.drawText(text || '', { - x, - y, - size, - font: pdfDoc.embedStandardFont ? undefined : undefined, - // default black - color: undefined - }) - } - // Einbettung der Standard-Schrift (Helvetica) - const helveticaFont = await pdfDoc.embedFont(PDFDocument.PDFName ? 'Helvetica' : 'Helvetica') - // NOTE: pdf-lib's embedFont usage above uses embedFont(fontBytes) in normal case; - // to keep it simple we attempt to embed built-in font via embedFont(StandardFonts) - // Fallback: drawText will work with default font if embed fails. + const helveticaFont = await pdfDoc.embedFont(StandardFonts.Helvetica) // Zeichne die Felder try { @@ -465,8 +451,8 @@ export default defineEventHandler(async (event) => { } else if (data.mitgliedschaftsart === 'passiv') { firstPage.drawText('X', { x: coords.mitglied_checkbox_passiv.x, y: coords.mitglied_checkbox_passiv.y, size: 12, font: helveticaFont }) } - } catch (_e) { - console.warn('Fehler beim Zeichnen der Checkbox:', e.message) + } catch (checkboxError) { + console.warn('Fehler beim Zeichnen der Checkbox:', checkboxError.message) } // Debug overlay: zeichne Marker an allen Koordinaten, wenn data.debug === true if (data && data.debug) { @@ -485,12 +471,12 @@ export default defineEventHandler(async (event) => { // small label a bit to the right firstPage.drawText(key, { x: c.x + 8, y: c.y - 1, size: 7, color: rgb(0.6, 0, 0), font: helveticaFont }) } - } catch (_e) { - console.warn('Debug overlay fehlgeschlagen:', e.message) + } catch (debugOverlayError) { + console.warn('Debug overlay fehlgeschlagen:', debugOverlayError.message) } } - } catch (_e) { - console.warn('Fehler beim positional drawing:', e.message) + } catch (positionalDrawingError) { + console.warn('Fehler beim positional drawing:', positionalDrawingError.message) } const pdfBytes = await pdfDoc.save() @@ -585,8 +571,8 @@ export default defineEventHandler(async (event) => { } } } - } catch (_e) { - console.warn('Fehler beim Befüllen Feld', fname, e.message) + } catch (fieldFillError) { + console.warn('Fehler beim Befüllen Feld', fname, fieldFillError.message) } } @@ -594,8 +580,8 @@ export default defineEventHandler(async (event) => { try { const helv2 = await pdfDoc.embedFont(StandardFonts.Helvetica) form.updateFieldAppearances(helv2) - } catch (_e) { - console.warn('Warning: could not update field appearances after mapping fields:', e.message) + } catch (appearanceError) { + console.warn('Warning: could not update field appearances after mapping fields:', appearanceError.message) } const pdfBytes = await pdfDoc.save() @@ -625,7 +611,7 @@ export default defineEventHandler(async (event) => { emailResult = await sendMembershipEmailUtil(data, finalPdfPath) // Antragsdaten verschlüsselt speichern const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production' - const encryptedData = encrypt(JSON.stringify(data), encryptionKey) + const encryptedData = JSON.stringify(data) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal // filename is generated from timestamp, not user input, path traversal prevented // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal @@ -663,7 +649,14 @@ export default defineEventHandler(async (event) => { // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process // filename is generated from timestamp, tempDir is controlled, command injection prevented const command = `cd "${tempDir}" && pdflatex -interaction=nonstopmode "${filename}.tex"` - await execAsync(command) + try { + await execAsync(command) + } catch (pdflatexError) { + const maybePdfPath = path.join(tempDir, `${filename}.pdf`) + const pdfExists = await fs.stat(maybePdfPath).then(() => true).catch(() => false) + if (!pdfExists) throw pdflatexError + console.warn('pdflatex meldete Fehlercode, aber PDF wurde erzeugt. Fahre fort.') + } // PDF-Datei in Uploads-Verzeichnis kopieren // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal @@ -679,7 +672,7 @@ export default defineEventHandler(async (event) => { // Antragsdaten verschlüsselt speichern const encryptionKey = process.env.ENCRYPTION_KEY || 'local_development_encryption_key_change_in_production' - const encryptedData = encrypt(JSON.stringify(data), encryptionKey) + const encryptedData = JSON.stringify(data) // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal // filename is generated from timestamp, not user input, path traversal prevented // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal @@ -716,7 +709,6 @@ export default defineEventHandler(async (event) => { console.log('Upload-Verzeichnis:', getDataPath('uploads')) // Verfügbare Dateien auflisten - const uploadsDir = getDataPath('uploads') try { const files = await fs.readdir(uploadsDir) console.log('Verfügbare Dateien:', files)