import { PDFDocument } from 'pdf-lib';
import { extrahiereEingebetteteDateien } from './pdf-attachment.util';
const XML = '';
async function pdfMitAnhang(
...anhaenge: { name: string; inhalt: string }[]
): Promise {
const doc = await PDFDocument.create();
doc.addPage();
for (const a of anhaenge) {
await doc.attach(Buffer.from(a.inhalt, 'utf8'), a.name, {
mimeType: 'text/xml',
});
}
return Buffer.from(await doc.save());
}
describe('extrahiereEingebetteteDateien', () => {
it('liest einen eingebetteten ZUGFeRD-Anhang aus', async () => {
const pdf = await pdfMitAnhang({ name: 'factur-x.xml', inhalt: XML });
const dateien = await extrahiereEingebetteteDateien(pdf);
expect(dateien).toHaveLength(1);
expect(dateien[0].name).toBe('factur-x.xml');
expect(dateien[0].inhalt.toString('utf8')).toBe(XML);
});
it('liefert jeden Anhang nur einmal, obwohl er in /Names und /AF steht', async () => {
const pdf = await pdfMitAnhang(
{ name: 'xrechnung.xml', inhalt: XML },
{ name: 'anhang.txt', inhalt: 'Lieferschein' },
);
const dateien = await extrahiereEingebetteteDateien(pdf);
expect(dateien.map((d) => d.name).sort()).toEqual([
'anhang.txt',
'xrechnung.xml',
]);
});
it('liefert eine leere Liste für ein PDF ohne Anhänge', async () => {
const doc = await PDFDocument.create();
doc.addPage();
const pdf = Buffer.from(await doc.save());
expect(await extrahiereEingebetteteDateien(pdf)).toEqual([]);
});
});
describe('extrahiereEingebetteteDateien – Dekompressionsbombe', () => {
it('lädt ein PDF mit Deflate-Bombe gar nicht erst', async () => {
// eslint-disable-next-line @typescript-eslint/no-require-imports
const zlib = require('zlib') as typeof import('zlib');
const bombe = zlib.deflateSync(Buffer.alloc(300 * 1024 * 1024));
const pdf = Buffer.concat([
Buffer.from(
`%PDF-1.7\n1 0 obj\n<< /Length ${bombe.length} /Filter /FlateDecode >>\nstream\n`,
'latin1',
),
bombe,
Buffer.from('\nendstream\nendobj\n%%EOF\n', 'latin1'),
]);
const load = jest.spyOn(PDFDocument, 'load');
await expect(extrahiereEingebetteteDateien(pdf)).resolves.toEqual([]);
expect(load).not.toHaveBeenCalled();
load.mockRestore();
});
});