Aufträge: PDF-Ablage mit Pfadprüfung
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
98eceaf14c
commit
e9e198e53c
2 files changed
+142
No files matched your search
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Ablage der gesendeten AB-PDFs unter config.auftraege.uploadDir (wird bei jedem Aufruf gelesen):
|
||||
* <uploadDir>/<kundenId>/<auftragId>.pdf, Verzeichnis 0700, Datei 0600. Überschreibt nie (die gesendete Fassung ist
|
||||
* der Beleg). Kennungen und Pfade sind auf [A-Za-z0-9_-] begrenzt (kein Punkt außer vor „pdf“), damit kein „..“ und
|
||||
* kein fremder Pfad möglich ist.
|
||||
*/
|
||||
import { createHash } from 'node:crypto'
|
||||
import { mkdir, readFile, unlink, writeFile } from 'node:fs/promises'
|
||||
import path from 'node:path'
|
||||
import { config } from '../../config.js'
|
||||
import { AuftragError } from './fehler.js'
|
||||
|
||||
const KENNUNG = /^[A-Za-z0-9_-]{1,64}$/
|
||||
const PDF_PFAD = /^[A-Za-z0-9_-]{1,64}\/[A-Za-z0-9_-]{1,64}\.pdf$/
|
||||
|
||||
const nichtGefunden = () => new AuftragError(404, 'PDF nicht gefunden.')
|
||||
const gueltigeKennung = (wert) => typeof wert === 'string' && KENNUNG.test(wert)
|
||||
|
||||
function pfadVon(pdfDatei) {
|
||||
if (typeof pdfDatei !== 'string' || !PDF_PFAD.test(pdfDatei)) throw nichtGefunden()
|
||||
return path.join(config.auftraege.uploadDir, pdfDatei)
|
||||
}
|
||||
|
||||
export async function speicherePdf(kundenId, auftragId, buffer) {
|
||||
if (!gueltigeKennung(kundenId) || !gueltigeKennung(auftragId)) throw new TypeError('Ungültige Kunden- oder Auftrags-ID für die PDF-Ablage.')
|
||||
if (!Buffer.isBuffer(buffer) || buffer.length === 0) throw new TypeError('Das PDF muss ein nicht leerer Buffer sein.')
|
||||
const pdfDatei = `${kundenId}/${auftragId}.pdf`
|
||||
const datei = path.join(config.auftraege.uploadDir, pdfDatei)
|
||||
await mkdir(path.dirname(datei), { recursive: true, mode: 0o700 })
|
||||
try {
|
||||
await writeFile(datei, buffer, { flag: 'wx', mode: 0o600 })
|
||||
} catch (err) {
|
||||
if (err?.code === 'EEXIST') throw new AuftragError(409, 'Das PDF dieser Auftragsbestätigung ist schon gespeichert.')
|
||||
// eine angelegte Teildatei nicht liegen lassen
|
||||
await unlink(datei).catch(() => {})
|
||||
throw err
|
||||
}
|
||||
return { pdfDatei, pdfSha256: createHash('sha256').update(buffer).digest('hex') }
|
||||
}
|
||||
|
||||
export async function lesePdf(pdfDatei) {
|
||||
const datei = pfadVon(pdfDatei)
|
||||
try {
|
||||
return await readFile(datei)
|
||||
} catch (err) {
|
||||
if (err?.code === 'ENOENT') throw nichtGefunden()
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
/** Räumt ein abgelegtes PDF weg, wenn danach etwas scheitert (L3). Fehlt die Datei, ist das folgenlos. */
|
||||
export async function entfernePdf(pdfDatei) {
|
||||
const datei = pfadVon(pdfDatei)
|
||||
try {
|
||||
await unlink(datei)
|
||||
} catch (err) {
|
||||
if (err?.code !== 'ENOENT') throw err
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user