From fcb21d9aad0f6476c62cb8fed4a47765b19b930f Mon Sep 17 00:00:00 2001 From: Mahathir Mohammad Shuvo Date: Sat, 22 Aug 2026 09:20:22 +0600 Subject: [PATCH] Throw from addNamedEmbeddedFile when no ref is given Calling it without a ref wrote the literal token `undefined` into the /EmbeddedFiles name tree, producing a PDF that does not parse. doc.file() already rejects a missing src; this makes the lower-level route agree. --- CHANGELOG.md | 1 + lib/document.js | 4 ++++ tests/unit/attachments.spec.js | 38 ++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f06eae9a..1e7f45b13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [BREAKING CHANGE] Remove the virtual file system (`pdfkit/virtual-fs`). Browser builds no longer depend on `fs`: pass a `Uint8Array`, `ArrayBuffer` or data URL to `registerFont`, `image` and `file` instead of a path - [BREAKING CHANGE] Restrict AcroForm options to documented mappings and explicit escape hatches. - [BREAKING CHANGE] Stop automatically uppercasing annotation option keys. +- [BREAKING CHANGE] Throw from `addNamedEmbeddedFile` when no ref is given, instead of writing an unparseable `undefined` token into the `/EmbeddedFiles` name tree - Do not mutate options passed to `doc.annotate()` and its convenience methods (link, note, strike, lineAnnotation, rectAnnotation, ellipseAnnotation, textAnnotation, fileAnnotation) - Persist font options when adding a new page. Fixes #1739 - Use `Uint8Array` instead of Node's `Buffer` internally diff --git a/lib/document.js b/lib/document.js index 4c7111626..78c723dec 100644 --- a/lib/document.js +++ b/lib/document.js @@ -232,6 +232,10 @@ class PDFDocument extends Readable { } addNamedEmbeddedFile(name, ref) { + if (!ref) { + throw new Error(`No ref specified for embedded file ${name}`); + } + if (!this._root.data.Names.data.EmbeddedFiles) { // disabling /Limits for this tree fixes attachments not showing in Adobe Reader this._root.data.Names.data.EmbeddedFiles = new PDFNameTree({ diff --git a/tests/unit/attachments.spec.js b/tests/unit/attachments.spec.js index d82376fdf..6e49129bc 100644 --- a/tests/unit/attachments.spec.js +++ b/tests/unit/attachments.spec.js @@ -241,6 +241,44 @@ describe('file', () => { (file1.txt) 10 0 R ] >> +>>`, + ]); + }); + + test('throws when the ref is missing', () => { + expect(() => + document.addNamedEmbeddedFile('phantom.txt', undefined), + ).toThrow('No ref specified for embedded file phantom.txt'); + + expect(() => document.addNamedEmbeddedFile('phantom.txt', null)).toThrow( + 'No ref specified for embedded file phantom.txt', + ); + }); + + test('registers a filespec created with the hidden option', () => { + const docData = logData(document); + + const filespec = document.file(Buffer.from('example text'), { + name: 'file.txt', + creationDate: date, + modifiedDate: date, + hidden: true, + }); + document.addNamedEmbeddedFile('file.txt', filespec); + document.end(); + + expect(docData).toContainChunk([ + `2 0 obj`, + `<< +/Dests << + /Names [ +] +>> +/EmbeddedFiles << + /Names [ + (file.txt) 9 0 R +] +>> >>`, ]); });