Skip to content

Show a PDF's attached files in a Files tab and save them - #92

Draft
pablopda wants to merge 1 commit into
Forward-Future:mainfrom
pablopda:feat/attachments-list-save
Draft

pablopda wants to merge 1 commit into
Forward-Future:mainfrom
pablopda:feat/attachments-list-save

Conversation

@pablopda

@pablopda pablopda commented Nov 11, 2026 •

Copy link
Copy Markdown

Problem

A PDF can carry files embedded in the document itself (a spreadsheet, a source file, a signed copy), but Folio gave no sign they were there and no way to get them out.

Change

When a PDF has files attached at the document level, the sidebar gets a Files tab next to Pages, Outline and Notes. It lists each file with its name and description, and the button on a row saves that file exactly as it was embedded. PDFs without attachments show no extra tab.

The names, descriptions and contents come from the PDF, so they are treated as untrusted:

  • A name is reduced to a plain file name before it is shown or offered for saving: no folders, no leading dot, no control, direction or invisible characters, no run of blanks that hides the ending, at most 255 characters.
  • At most 500 files are listed, and a file that unpacks to more than 300 MB (the existing PDF size limit) is not saved.
  • The file is saved as plain binary, so the offered name doesn't gain an extension guessed from the contents.

Files attached to a page as annotations, and reading attached PDFs in their own tabs, are left out, as agreed in the issue. The README's "Read and navigate" list has one new line.

Two things I left for your judgement rather than deciding here:

  • A file such as invoice.pdf.exe is saved under that name without a warning. The reader always picks the location in the save dialog and nothing is opened. I didn't check whether Windows or macOS mark the saved file as downloaded.
  • The 500-file limit bounds what Folio keeps and draws. pdf.js still reads the whole attachment list in its worker when asked, so a PDF made to have hundreds of thousands of entries can still slow that document down.

Issue or approval

Refs #53. Scope approved in #53 (comment) ("lists attachments embedded at the document level and lets the reader save one. A small PDF fixture can verify that the saved bytes match the attachment.")

How I checked it

  • src/attachments.test.ts builds a small PDF with pdf-lib and checks, against real pdf.js, the listed names and descriptions, that each file's bytes come back identical (a buffer with every byte value), the list and size limits, and the name cleanup.
  • The new smoke check (scripts/smoke-attachments.mjs, called from checkReaderTools) opens a PDF with a binary file, a text file and a file with no extension, saves each one through the Files tab, and compares the saved files with the originals byte for byte and the offered names with the listed ones. It also checks that a PDF without attachments has no Files tab.
  • Windows, macOS, and Linux CI passed in my fork on commit ba982ec: type check, unit tests, build and the full smoke suite.
  • Locally on Linux (Ubuntu): npx tsc --noEmit, npm test, and the new smoke check with a hidden window.

Not checked: Windows and macOS by hand (CI only), and the save dialog itself, which the smoke check bypasses by setting the save path.

Before and after, the same PDF with three attached files:

Screenshots will be attached here before this is marked ready for review.

Written and tested with Claude Fable 5.2 in Claude Code.

🤖 Generated with Claude Code

When a PDF has files embedded at the document level, the sidebar gets a
Files tab that lists them and saves each one exactly as it was attached.
PDFs without attachments show no extra tab. Names come from the PDF, so
they are reduced to a plain file name before being shown or saved.

Files attached to a page as annotations and reading attached PDFs in
their own tabs are left out, as agreed in Forward-Future#53.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant