Commit e6555a36f0fdf4236acb76a33ba92be98615512c

Authored by m-holger
1 parent 8d762145

Integrate `QPDFEmbeddedFileDocumentHelper` with `QPDF` to streamline embedded fi…

…le handling. Add shared helper retrieval, validation methods, and update usages across the codebase.
examples/pdf-attach-file.cc
@@ -79,7 +79,7 @@ process( @@ -79,7 +79,7 @@ process(
79 } 79 }
80 80
81 // Add the embedded file at the document level as an attachment. 81 // Add the embedded file at the document level as an attachment.
82 - auto efdh = QPDFEmbeddedFileDocumentHelper(q); 82 + auto& efdh = QPDFEmbeddedFileDocumentHelper::get(q);
83 efdh.replaceEmbeddedFile(key, fs); 83 efdh.replaceEmbeddedFile(key, fs);
84 84
85 // Create a file attachment annotation. 85 // Create a file attachment annotation.
include/qpdf/QPDF.hh
@@ -63,6 +63,7 @@ class BufferInputSource; @@ -63,6 +63,7 @@ class BufferInputSource;
63 class QPDFLogger; 63 class QPDFLogger;
64 class QPDFParser; 64 class QPDFParser;
65 class QPDFAcroFormDocumentHelper; 65 class QPDFAcroFormDocumentHelper;
  66 +class QPDFEmbeddedFileDocumentHelper;
66 class QPDFPageLabelDocumentHelper; 67 class QPDFPageLabelDocumentHelper;
67 68
68 class QPDF 69 class QPDF
@@ -800,6 +801,7 @@ class QPDF @@ -800,6 +801,7 @@ class QPDF
800 801
801 inline bool reconstructed_xref() const; 802 inline bool reconstructed_xref() const;
802 inline QPDFAcroFormDocumentHelper& acroform(); 803 inline QPDFAcroFormDocumentHelper& acroform();
  804 + inline QPDFEmbeddedFileDocumentHelper& embedded_files();
803 inline QPDFPageLabelDocumentHelper& page_labels(); 805 inline QPDFPageLabelDocumentHelper& page_labels();
804 806
805 // For testing only -- do not add to DLL 807 // For testing only -- do not add to DLL
include/qpdf/QPDFEmbeddedFileDocumentHelper.hh
@@ -36,6 +36,21 @@ @@ -36,6 +36,21 @@
36 class QPDFEmbeddedFileDocumentHelper: public QPDFDocumentHelper 36 class QPDFEmbeddedFileDocumentHelper: public QPDFDocumentHelper
37 { 37 {
38 public: 38 public:
  39 + // Get a shared document helper for a given QPDF object.
  40 + //
  41 + // Retrieving a document helper for a QPDF object rather than creating a new one avoids repeated
  42 + // validation of the EmbeddedFiles structure, which can be expensive.
  43 + QPDF_DLL
  44 + static QPDFEmbeddedFileDocumentHelper& get(QPDF& qpdf);
  45 +
  46 + // Re-validate the EmbeddedFiles structure. This is useful if you have modified the structure of
  47 + // the EmbeddedFiles dictionary in a way that would invalidate the cache.
  48 + //
  49 + // If repair is true, the document will be repaired if possible if the validation encounters
  50 + // errors.
  51 + QPDF_DLL
  52 + void validate(bool repair = true);
  53 +
39 QPDF_DLL 54 QPDF_DLL
40 QPDFEmbeddedFileDocumentHelper(QPDF&); 55 QPDFEmbeddedFileDocumentHelper(QPDF&);
41 56
libqpdf/QPDFEmbeddedFileDocumentHelper.cc
1 #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> 1 #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
2 2
  3 +#include <qpdf/QPDFNameTreeObjectHelper.hh>
  4 +#include <qpdf/QPDF_private.hh>
  5 +
3 // File attachments are stored in the /EmbeddedFiles (name tree) key of the /Names dictionary from 6 // File attachments are stored in the /EmbeddedFiles (name tree) key of the /Names dictionary from
4 // the document catalog. Each entry points to a /FileSpec, which in turn points to one more Embedded 7 // the document catalog. Each entry points to a /FileSpec, which in turn points to one more Embedded
5 // File Streams. Note that file specs can appear in other places as well, such as file attachment 8 // File Streams. Note that file specs can appear in other places as well, such as file attachment
@@ -44,6 +47,19 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF&amp; qpdf) : @@ -44,6 +47,19 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF&amp; qpdf) :
44 QPDFDocumentHelper(qpdf), 47 QPDFDocumentHelper(qpdf),
45 m(std::make_shared<Members>()) 48 m(std::make_shared<Members>())
46 { 49 {
  50 + validate();
  51 +}
  52 +
  53 +QPDFEmbeddedFileDocumentHelper&
  54 +QPDFEmbeddedFileDocumentHelper::get(QPDF& qpdf)
  55 +{
  56 + return qpdf.embedded_files();
  57 +}
  58 +
  59 +void
  60 +QPDFEmbeddedFileDocumentHelper::validate(bool repair)
  61 +{
  62 + m->embedded_files.reset();
47 auto names = qpdf.getRoot().getKey("/Names"); 63 auto names = qpdf.getRoot().getKey("/Names");
48 if (names.isDictionary()) { 64 if (names.isDictionary()) {
49 auto embedded_files = names.getKey("/EmbeddedFiles"); 65 auto embedded_files = names.getKey("/EmbeddedFiles");
@@ -53,7 +69,7 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF&amp; qpdf) : @@ -53,7 +69,7 @@ QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF&amp; qpdf) :
53 qpdf, 69 qpdf,
54 [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); }, 70 [](QPDFObjectHandle const& o) -> bool { return o.isDictionary(); },
55 true); 71 true);
56 - m->embedded_files->validate(); 72 + m->embedded_files->validate(repair);
57 } 73 }
58 } 74 }
59 } 75 }
libqpdf/QPDFJob.cc
@@ -13,15 +13,12 @@ @@ -13,15 +13,12 @@
13 #include <qpdf/Pl_StdioFile.hh> 13 #include <qpdf/Pl_StdioFile.hh>
14 #include <qpdf/Pl_String.hh> 14 #include <qpdf/Pl_String.hh>
15 #include <qpdf/QIntC.hh> 15 #include <qpdf/QIntC.hh>
16 -#include <qpdf/QPDFAcroFormDocumentHelper.hh>  
17 #include <qpdf/QPDFCryptoProvider.hh> 16 #include <qpdf/QPDFCryptoProvider.hh>
18 -#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>  
19 #include <qpdf/QPDFExc.hh> 17 #include <qpdf/QPDFExc.hh>
20 #include <qpdf/QPDFLogger.hh> 18 #include <qpdf/QPDFLogger.hh>
21 #include <qpdf/QPDFObjectHandle_private.hh> 19 #include <qpdf/QPDFObjectHandle_private.hh>
22 #include <qpdf/QPDFOutlineDocumentHelper.hh> 20 #include <qpdf/QPDFOutlineDocumentHelper.hh>
23 #include <qpdf/QPDFPageDocumentHelper.hh> 21 #include <qpdf/QPDFPageDocumentHelper.hh>
24 -#include <qpdf/QPDFPageLabelDocumentHelper.hh>  
25 #include <qpdf/QPDFPageObjectHelper.hh> 22 #include <qpdf/QPDFPageObjectHelper.hh>
26 #include <qpdf/QPDFSystemError.hh> 23 #include <qpdf/QPDFSystemError.hh>
27 #include <qpdf/QPDFUsage.hh> 24 #include <qpdf/QPDFUsage.hh>
@@ -894,7 +891,7 @@ QPDFJob::doShowPages(QPDF&amp; pdf) @@ -894,7 +891,7 @@ QPDFJob::doShowPages(QPDF&amp; pdf)
894 void 891 void
895 QPDFJob::doListAttachments(QPDF& pdf) 892 QPDFJob::doListAttachments(QPDF& pdf)
896 { 893 {
897 - QPDFEmbeddedFileDocumentHelper efdh(pdf); 894 + auto& efdh = pdf.embedded_files();
898 if (efdh.hasEmbeddedFiles()) { 895 if (efdh.hasEmbeddedFiles()) {
899 for (auto const& i: efdh.getEmbeddedFiles()) { 896 for (auto const& i: efdh.getEmbeddedFiles()) {
900 std::string const& key = i.first; 897 std::string const& key = i.first;
@@ -934,7 +931,7 @@ QPDFJob::doListAttachments(QPDF&amp; pdf) @@ -934,7 +931,7 @@ QPDFJob::doListAttachments(QPDF&amp; pdf)
934 void 931 void
935 QPDFJob::doShowAttachment(QPDF& pdf) 932 QPDFJob::doShowAttachment(QPDF& pdf)
936 { 933 {
937 - QPDFEmbeddedFileDocumentHelper efdh(pdf); 934 + auto& efdh = pdf.embedded_files();
938 auto fs = efdh.getEmbeddedFile(m->attachment_to_show); 935 auto fs = efdh.getEmbeddedFile(m->attachment_to_show);
939 if (!fs) { 936 if (!fs) {
940 throw std::runtime_error("attachment " + m->attachment_to_show + " not found"); 937 throw std::runtime_error("attachment " + m->attachment_to_show + " not found");
@@ -1321,7 +1318,7 @@ QPDFJob::doJSONAttachments(Pipeline* p, bool&amp; first, QPDF&amp; pdf) @@ -1321,7 +1318,7 @@ QPDFJob::doJSONAttachments(Pipeline* p, bool&amp; first, QPDF&amp; pdf)
1321 }; 1318 };
1322 1319
1323 JSON j_attachments = JSON::makeDictionary(); 1320 JSON j_attachments = JSON::makeDictionary();
1324 - QPDFEmbeddedFileDocumentHelper efdh(pdf); 1321 + auto& efdh = pdf.embedded_files();
1325 for (auto const& iter: efdh.getEmbeddedFiles()) { 1322 for (auto const& iter: efdh.getEmbeddedFiles()) {
1326 std::string const& key = iter.first; 1323 std::string const& key = iter.first;
1327 auto fsoh = iter.second; 1324 auto fsoh = iter.second;
@@ -2073,7 +2070,7 @@ void @@ -2073,7 +2070,7 @@ void
2073 QPDFJob::addAttachments(QPDF& pdf) 2070 QPDFJob::addAttachments(QPDF& pdf)
2074 { 2071 {
2075 maybe_set_pagemode(pdf, "/UseAttachments"); 2072 maybe_set_pagemode(pdf, "/UseAttachments");
2076 - QPDFEmbeddedFileDocumentHelper efdh(pdf); 2073 + auto& efdh = pdf.embedded_files();
2077 std::vector<std::string> duplicated_keys; 2074 std::vector<std::string> duplicated_keys;
2078 for (auto const& to_add: m->attachments_to_add) { 2075 for (auto const& to_add: m->attachments_to_add) {
2079 if ((!to_add.replace) && efdh.getEmbeddedFile(to_add.key)) { 2076 if ((!to_add.replace) && efdh.getEmbeddedFile(to_add.key)) {
@@ -2117,7 +2114,7 @@ void @@ -2117,7 +2114,7 @@ void
2117 QPDFJob::copyAttachments(QPDF& pdf) 2114 QPDFJob::copyAttachments(QPDF& pdf)
2118 { 2115 {
2119 maybe_set_pagemode(pdf, "/UseAttachments"); 2116 maybe_set_pagemode(pdf, "/UseAttachments");
2120 - QPDFEmbeddedFileDocumentHelper efdh(pdf); 2117 + auto& efdh = pdf.embedded_files();
2121 std::vector<std::string> duplicates; 2118 std::vector<std::string> duplicates;
2122 for (auto const& to_copy: m->attachments_to_copy) { 2119 for (auto const& to_copy: m->attachments_to_copy) {
2123 doIfVerbose([&](Pipeline& v, std::string const& prefix) { 2120 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
@@ -2125,7 +2122,7 @@ QPDFJob::copyAttachments(QPDF&amp; pdf) @@ -2125,7 +2122,7 @@ QPDFJob::copyAttachments(QPDF&amp; pdf)
2125 }); 2122 });
2126 std::unique_ptr<QPDF> other; 2123 std::unique_ptr<QPDF> other;
2127 processFile(other, to_copy.path.c_str(), to_copy.password.c_str(), false, false); 2124 processFile(other, to_copy.path.c_str(), to_copy.password.c_str(), false, false);
2128 - QPDFEmbeddedFileDocumentHelper other_efdh(*other); 2125 + auto& other_efdh = other->embedded_files();
2129 auto other_attachments = other_efdh.getEmbeddedFiles(); 2126 auto other_attachments = other_efdh.getEmbeddedFiles();
2130 for (auto const& iter: other_attachments) { 2127 for (auto const& iter: other_attachments) {
2131 std::string new_key = to_copy.prefix + iter.first; 2128 std::string new_key = to_copy.prefix + iter.first;
@@ -2259,7 +2256,7 @@ QPDFJob::handleTransformations(QPDF&amp; pdf) @@ -2259,7 +2256,7 @@ QPDFJob::handleTransformations(QPDF&amp; pdf)
2259 pdf.getRoot().replaceKey("/PageLabels", page_labels); 2256 pdf.getRoot().replaceKey("/PageLabels", page_labels);
2260 } 2257 }
2261 if (!m->attachments_to_remove.empty()) { 2258 if (!m->attachments_to_remove.empty()) {
2262 - QPDFEmbeddedFileDocumentHelper efdh(pdf); 2259 + auto& efdh = pdf.embedded_files();
2263 for (auto const& key: m->attachments_to_remove) { 2260 for (auto const& key: m->attachments_to_remove) {
2264 if (efdh.removeEmbeddedFile(key)) { 2261 if (efdh.removeEmbeddedFile(key)) {
2265 doIfVerbose([&](Pipeline& v, std::string const& prefix) { 2262 doIfVerbose([&](Pipeline& v, std::string const& prefix) {
libqpdf/qpdf/QPDF_private.hh
@@ -4,6 +4,7 @@ @@ -4,6 +4,7 @@
4 #include <qpdf/QPDF.hh> 4 #include <qpdf/QPDF.hh>
5 5
6 #include <qpdf/QPDFAcroFormDocumentHelper.hh> 6 #include <qpdf/QPDFAcroFormDocumentHelper.hh>
  7 +#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
7 #include <qpdf/QPDFObject_private.hh> 8 #include <qpdf/QPDFObject_private.hh>
8 #include <qpdf/QPDFPageLabelDocumentHelper.hh> 9 #include <qpdf/QPDFPageLabelDocumentHelper.hh>
9 #include <qpdf/QPDFTokenizer_private.hh> 10 #include <qpdf/QPDFTokenizer_private.hh>
@@ -554,6 +555,7 @@ class QPDF::Members @@ -554,6 +555,7 @@ class QPDF::Members
554 555
555 // Document Helpers; 556 // Document Helpers;
556 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform; 557 std::unique_ptr<QPDFAcroFormDocumentHelper> acroform;
  558 + std::unique_ptr<QPDFEmbeddedFileDocumentHelper> embedded_files;
557 std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels; 559 std::unique_ptr<QPDFPageLabelDocumentHelper> page_labels;
558 }; 560 };
559 561
@@ -586,6 +588,15 @@ QPDF::acroform() @@ -586,6 +588,15 @@ QPDF::acroform()
586 return *m->acroform; 588 return *m->acroform;
587 } 589 }
588 590
  591 +inline QPDFEmbeddedFileDocumentHelper&
  592 +QPDF::embedded_files()
  593 +{
  594 + if (!m->embedded_files) {
  595 + m->embedded_files = std::make_unique<QPDFEmbeddedFileDocumentHelper>(*this);
  596 + }
  597 + return *m->embedded_files;
  598 +}
  599 +
589 inline QPDFPageLabelDocumentHelper& 600 inline QPDFPageLabelDocumentHelper&
590 QPDF::page_labels() 601 QPDF::page_labels()
591 { 602 {
qpdf/test_driver.cc
@@ -2624,7 +2624,7 @@ test_76(QPDF&amp; pdf, char const* arg2) @@ -2624,7 +2624,7 @@ test_76(QPDF&amp; pdf, char const* arg2)
2624 { 2624 {
2625 // Embedded files. arg2 is a file to attach. Hard-code the 2625 // Embedded files. arg2 is a file to attach. Hard-code the
2626 // mime type and file name for test purposes. 2626 // mime type and file name for test purposes.
2627 - QPDFEmbeddedFileDocumentHelper efdh(pdf); 2627 + auto &efdh = QPDFEmbeddedFileDocumentHelper::get(pdf);
2628 auto fs1 = QPDFFileSpecObjectHelper::createFileSpec(pdf, "att1.txt", arg2); 2628 auto fs1 = QPDFFileSpecObjectHelper::createFileSpec(pdf, "att1.txt", arg2);
2629 fs1.setDescription("some text"); 2629 fs1.setDescription("some text");
2630 auto efs1 = QPDFEFStreamObjectHelper(fs1.getEmbeddedFileStream()); 2630 auto efs1 = QPDFEFStreamObjectHelper(fs1.getEmbeddedFileStream());