Skip to content
Retrieving Attached Files

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:

FieldOn read
Content, Hash, Name, RelationshipPopulated.
DescriptionPopulated, but as the raw PDF serialization.
FileTypeAlways empty.
CreationTime, ModTimeAlways 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.go

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

View the full source
Last updated on