1 #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
2 
3 // File attachments are stored in the /EmbeddedFiles (name tree) key
4 // of the /Names dictionary from the document catalog. Each entry
5 // points to a /FileSpec, which in turn points to one more Embedded
6 // File Streams. Note that file specs can appear in other places as
7 // well, such as file attachment annotations, among others.
8 //
9 // root -> /Names -> /EmbeddedFiles = name tree
10 // filename -> filespec
11 // <<
12 //   /Desc ()
13 //   /EF <<
14 //     /F x 0 R
15 //     /UF x 0 R
16 //   >>
17 //   /F (name)
18 //   /UF (name)
19 //   /Type /Filespec
20 // >>
21 // x 0 obj
22 // <<
23 //   /Type /EmbeddedFile
24 //   /DL filesize % not in spec?
25 //   /Params <<
26 //     /CheckSum <md5>
27 //     /CreationDate (D:yyyymmddhhmmss{-hh'mm'|+hh'mm'|Z})
28 //     /ModDate (D:yyyymmddhhmmss-hh'mm')
29 //     /Size filesize
30 //     /Subtype /mime#2ftype
31 //   >>
32 // >>
33 
QPDFEmbeddedFileDocumentHelper(QPDF & qpdf)34 QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) :
35     QPDFDocumentHelper(qpdf),
36     m(new Members())
37 {
38     auto root = qpdf.getRoot();
39     auto names = root.getKey("/Names");
40     if (names.isDictionary())
41     {
42         auto embedded_files = names.getKey("/EmbeddedFiles");
43         if (embedded_files.isDictionary())
44         {
45             this->m->embedded_files =
46                 std::make_shared<QPDFNameTreeObjectHelper>(
47                     embedded_files, qpdf);
48         }
49     }
50 }
51 
Members()52 QPDFEmbeddedFileDocumentHelper::Members::Members()
53 {
54 }
55 
56 bool
hasEmbeddedFiles() const57 QPDFEmbeddedFileDocumentHelper::hasEmbeddedFiles() const
58 {
59     return (this->m->embedded_files.get() != nullptr);
60 }
61 
62 void
initEmbeddedFiles()63 QPDFEmbeddedFileDocumentHelper::initEmbeddedFiles()
64 {
65     if (hasEmbeddedFiles())
66     {
67         return;
68     }
69     auto root = qpdf.getRoot();
70     auto names = root.getKey("/Names");
71     if (! names.isDictionary())
72     {
73         names = QPDFObjectHandle::newDictionary();
74         root.replaceKey("/Names", names);
75     }
76     auto embedded_files = names.getKey("/EmbeddedFiles");
77     if (! embedded_files.isDictionary())
78     {
79         auto nth = QPDFNameTreeObjectHelper::newEmpty(this->qpdf);
80         names.replaceKey("/EmbeddedFiles", nth.getObjectHandle());
81         this->m->embedded_files =
82             std::make_shared<QPDFNameTreeObjectHelper>(nth);
83     }
84 }
85 
86 std::shared_ptr<QPDFFileSpecObjectHelper>
getEmbeddedFile(std::string const & name)87 QPDFEmbeddedFileDocumentHelper::getEmbeddedFile(std::string const& name)
88 {
89     std::shared_ptr<QPDFFileSpecObjectHelper> result;
90     if (this->m->embedded_files)
91     {
92         auto i = this->m->embedded_files->find(name);
93         if (i != this->m->embedded_files->end())
94         {
95             result = std::make_shared<QPDFFileSpecObjectHelper>(i->second);
96         }
97     }
98     return result;
99 }
100 
101 std::map<std::string, std::shared_ptr<QPDFFileSpecObjectHelper>>
getEmbeddedFiles()102 QPDFEmbeddedFileDocumentHelper::getEmbeddedFiles()
103 {
104     std::map<std::string,
105              std::shared_ptr<QPDFFileSpecObjectHelper>> result;
106     if (this->m->embedded_files)
107     {
108         for (auto const& i: *(this->m->embedded_files))
109         {
110             result[i.first] = std::make_shared<QPDFFileSpecObjectHelper>(
111                 i.second);
112         }
113     }
114     return result;
115 }
116 
117 void
replaceEmbeddedFile(std::string const & name,QPDFFileSpecObjectHelper const & fs)118 QPDFEmbeddedFileDocumentHelper::replaceEmbeddedFile(
119     std::string const& name, QPDFFileSpecObjectHelper const& fs)
120 {
121     initEmbeddedFiles();
122     this->m->embedded_files->insert(
123         name, fs.getObjectHandle());
124 }
125 
126 bool
removeEmbeddedFile(std::string const & name)127 QPDFEmbeddedFileDocumentHelper::removeEmbeddedFile(std::string const& name)
128 {
129     if (! hasEmbeddedFiles())
130     {
131         return false;
132     }
133     auto iter = this->m->embedded_files->find(name);
134     if (iter == this->m->embedded_files->end())
135     {
136         return false;
137     }
138     auto oh = iter->second;
139     iter.remove();
140     if (oh.isIndirect())
141     {
142         this->qpdf.replaceObject(oh.getObjGen(), QPDFObjectHandle::newNull());
143     }
144 
145     return true;
146 }
147