Retrieving Attached Files
PdfReader.GetAttachedFiles returns every file in the document’s /Names
/EmbeddedFiles tree, already decompressed, as *model.EmbeddedFile values. It
is the read side of attaching a file, and the only listing
method there is: the writer has none.
Doing it
files, err := pdfReader.GetAttachedFiles()
if err != nil {
return err
}
for _, f := range files {
if err := os.WriteFile(f.Name, f.Content, 0644); err != nil {
return err
}
fmt.Printf("Name: %s; Hash: %s\n", f.Name, f.Hash)
}Content holds the decoded bytes, so there is nothing to decompress yourself.
Name comes from the file specification’s F entry, which is the name the writer
put there, and it is not sanitized: treat it as untrusted input before joining it
to a path.
Hash is the CheckSum stored in the stream’s parameters. UniPDF writes an MD5
of the content as a hex string, so comparing it against your own MD5 of Content
detects a corrupted attachment. Other producers store the raw 16 bytes instead,
so a checksum that prints as garbage is not necessarily wrong.
A document with no name dictionary at all gives an empty slice and no error; one
with a name dictionary but no EmbeddedFiles entry gives a nil slice and no
error. Ranging over the result covers both.
What does not round-trip
Only part of EmbeddedFile is filled in on the way back:
| Field | On read |
|---|---|
Content, Hash, Name, Relationship | Populated. |
Description | Populated, but as the raw PDF serialization. |
FileType | Always empty. |
CreationTime, ModTime | Always zero, even though the stream carries CreationDate and ModDate. |
Description is the one to watch. It is taken from the serialized form of the
filespec’s Desc object rather than its decoded value, so a description written
as Source XML reads back as (Source XML), parentheses included, and a
UTF-16 description reads back as a hex string like <feff0053...>. Strip the
delimiters yourself, or read Desc off the filespec and decode it if you need the
original text.
FileType being empty means you cannot tell an attachment’s type from the API.
The extension in Name is what you have, which is also all a viewer has, since
the writer records text/plain as the stream subtype for every attachment.
Limitations
Only document-level attachments are returned. A file attached as a page annotation, the route file attachment annotations covers, does not appear here unless it was also added to the name tree. To collect those, walk the page annotations instead.
The name tree is read one level deep. GetAttachedFiles expects EmbeddedFiles
to carry a Names array directly, and returns an “Invalid Names array” error for
a document whose tree is split across intermediate Kids nodes. Documents written
by UniPDF are flat, so this shows up on files from other producers with many
attachments.
A file specification with no Desc entry is not handled: reading the missing
description dereferences a nil object and panics. UniPDF always writes Desc,
so this too is a hazard only with third-party documents. Recover around the call
if you process files you did not produce.
Every attachment is decompressed into memory in full, so a document with large attachments costs the sum of their sizes.
Run the example
listAttachments opens output.pdf, calls GetAttachedFiles, and writes each
attachment into an output directory. That input is what
pdf_add_attachment.go produces, so run that first in the same folder. The
example appends .xml to every name it writes, on the assumption that the
attachments are the XML files the other example attached.
git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/attachment
go run pdf_get_attachment.goIf this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.