From ad34b9c278608dfdcfdbe7402acb3a6dd04c3d0e Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Mon, 8 Feb 2021 18:07:21 -0500 Subject: Implement helpers for file attachments --- libqpdf/QPDFEFStreamObjectHelper.cc | 193 ++++++++++++++++++++++++++++++ libqpdf/QPDFEmbeddedFileDocumentHelper.cc | 146 ++++++++++++++++++++++ libqpdf/QPDFFileSpecObjectHelper.cc | 157 ++++++++++++++++++++++++ libqpdf/build.mk | 3 + 4 files changed, 499 insertions(+) create mode 100644 libqpdf/QPDFEFStreamObjectHelper.cc create mode 100644 libqpdf/QPDFEmbeddedFileDocumentHelper.cc create mode 100644 libqpdf/QPDFFileSpecObjectHelper.cc (limited to 'libqpdf') diff --git a/libqpdf/QPDFEFStreamObjectHelper.cc b/libqpdf/QPDFEFStreamObjectHelper.cc new file mode 100644 index 00000000..c4e64a71 --- /dev/null +++ b/libqpdf/QPDFEFStreamObjectHelper.cc @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include + +QPDFEFStreamObjectHelper::QPDFEFStreamObjectHelper( + QPDFObjectHandle oh) : + QPDFObjectHelper(oh), + m(new Members()) +{ +} + +QPDFEFStreamObjectHelper::Members::Members() +{ +} + +QPDFObjectHandle +QPDFEFStreamObjectHelper::getParam(std::string const& pkey) +{ + auto params = this->oh.getDict().getKey("/Params"); + if (params.isDictionary()) + { + return params.getKey(pkey); + } + return QPDFObjectHandle::newNull(); +} + +void +QPDFEFStreamObjectHelper::setParam( + std::string const& pkey, QPDFObjectHandle const& pval) +{ + auto params = this->oh.getDict().getKey("/Params"); + if (! params.isDictionary()) + { + params = QPDFObjectHandle::newDictionary(); + this->oh.getDict().replaceKey("/Params", params); + } + params.replaceKey(pkey, pval); +} + +std::string +QPDFEFStreamObjectHelper::getCreationDate() +{ + auto val = getParam("/CreationDate"); + if (val.isString()) + { + return val.getUTF8Value(); + } + return ""; +} + +std::string +QPDFEFStreamObjectHelper::getModDate() +{ + auto val = getParam("/ModDate"); + if (val.isString()) + { + return val.getUTF8Value(); + } + return ""; +} + +size_t +QPDFEFStreamObjectHelper::getSize() +{ + auto val = getParam("/Size"); + if (val.isInteger()) + { + return QIntC::to_size(val.getUIntValueAsUInt()); + } + return 0; +} + +std::string +QPDFEFStreamObjectHelper::getSubtype() +{ + auto val = getParam("/Subtype"); + if (val.isName()) + { + auto n = val.getName(); + if (n.length() > 1) + { + return n.substr(1); + } + } + return ""; +} + +std::string +QPDFEFStreamObjectHelper::getChecksum() +{ + auto val = getParam("/CheckSum"); + if (val.isString()) + { + return val.getStringValue(); + } + return ""; +} + +QPDFEFStreamObjectHelper +QPDFEFStreamObjectHelper::createEFStream( + QPDF& qpdf, PointerHolder data) +{ + return newFromStream(QPDFObjectHandle::newStream(&qpdf, data)); +} + +QPDFEFStreamObjectHelper +QPDFEFStreamObjectHelper::createEFStream( + QPDF& qpdf, std::string const& data) +{ + return newFromStream(QPDFObjectHandle::newStream(&qpdf, data)); +} + +namespace QEF +{ + class Provider: public QPDFObjectHandle::StreamDataProvider + { + public: + Provider(std::function provider) : + StreamDataProvider(false), + provider(provider) + { + } + virtual ~Provider() = default; + virtual void provideStreamData(int objid, int generation, + Pipeline* pipeline) override + { + this->provider(pipeline); + } + + private: + std::function provider; + }; +}; + +QPDFEFStreamObjectHelper +QPDFEFStreamObjectHelper::createEFStream( + QPDF& qpdf, std::function provider) +{ + auto stream = QPDFObjectHandle::newStream(&qpdf); + stream.replaceStreamData(new QEF::Provider(provider), + QPDFObjectHandle::newNull(), + QPDFObjectHandle::newNull()); + return newFromStream(stream); +} + +QPDFEFStreamObjectHelper& +QPDFEFStreamObjectHelper::setCreationDate(std::string const& date) +{ + setParam("/CreationDate", QPDFObjectHandle::newString(date)); + return *this; +} + +QPDFEFStreamObjectHelper& +QPDFEFStreamObjectHelper::setModDate(std::string const& date) +{ + setParam("/ModDate", QPDFObjectHandle::newString(date)); + return *this; +} + +QPDFEFStreamObjectHelper& +QPDFEFStreamObjectHelper::setSubtype(std::string const& subtype) +{ + setParam("/Subtype", QPDFObjectHandle::newName("/" + subtype)); + return *this; +} + +QPDFEFStreamObjectHelper +QPDFEFStreamObjectHelper::newFromStream(QPDFObjectHandle stream) +{ + QPDFEFStreamObjectHelper result(stream); + stream.getDict().replaceKey( + "/Type", QPDFObjectHandle::newName("/EmbeddedFile")); + Pl_Discard discard; + Pl_MD5 md5("EF md5", &discard); + Pl_Count count("EF size", &md5); + if (! stream.pipeStreamData(&count, nullptr, 0, qpdf_dl_all)) + { + stream.warnIfPossible( + "unable to get stream data for new embedded file stream"); + } + else + { + result.setParam( + "/Size", QPDFObjectHandle::newInteger(count.getCount())); + result.setParam( + "/CheckSum", QPDFObjectHandle::newString( + QUtil::hex_decode(md5.getHexDigest()))); + } + return result; +} diff --git a/libqpdf/QPDFEmbeddedFileDocumentHelper.cc b/libqpdf/QPDFEmbeddedFileDocumentHelper.cc new file mode 100644 index 00000000..6348529d --- /dev/null +++ b/libqpdf/QPDFEmbeddedFileDocumentHelper.cc @@ -0,0 +1,146 @@ +#include + +// File attachments are stored in the /EmbeddedFiles (name tree) key +// of the /Names dictionary from the document catalog. Each entry +// points to a /FileSpec, which in turn points to one more Embedded +// File Streams. Note that file specs can appear in other places as +// well, such as file attachment annotations, among others. +// +// root -> /Names -> /EmbeddedFiles = name tree +// filename -> filespec +// << +// /Desc () +// /EF << +// /F x 0 R +// /UF x 0 R +// >> +// /F (name) +// /UF (name) +// /Type /Filespec +// >> +// x 0 obj +// << +// /Type /EmbeddedFile +// /DL filesize % not in spec? +// /Params << +// /CheckSum +// /CreationDate (D:yyyymmddhhmmss{-hh'mm'|+hh'mm'|Z}) +// /ModDate (D:yyyymmddhhmmss-hh'mm') +// /Size filesize +// /Subtype /mime#2ftype +// >> +// >> + +QPDFEmbeddedFileDocumentHelper::QPDFEmbeddedFileDocumentHelper(QPDF& qpdf) : + QPDFDocumentHelper(qpdf), + m(new Members()) +{ + auto root = qpdf.getRoot(); + auto names = root.getKey("/Names"); + if (names.isDictionary()) + { + auto embedded_files = names.getKey("/EmbeddedFiles"); + if (embedded_files.isDictionary()) + { + this->m->embedded_files = + std::make_shared( + embedded_files, qpdf); + } + } +} + +QPDFEmbeddedFileDocumentHelper::Members::Members() +{ +} + +bool +QPDFEmbeddedFileDocumentHelper::hasEmbeddedFiles() const +{ + return (this->m->embedded_files.get() != nullptr); +} + +void +QPDFEmbeddedFileDocumentHelper::initEmbeddedFiles() +{ + if (hasEmbeddedFiles()) + { + return; + } + auto root = qpdf.getRoot(); + auto names = root.getKey("/Names"); + if (! names.isDictionary()) + { + names = QPDFObjectHandle::newDictionary(); + root.replaceKey("/Names", names); + } + auto embedded_files = names.getKey("/EmbeddedFiles"); + if (! embedded_files.isDictionary()) + { + auto nth = QPDFNameTreeObjectHelper::newEmpty(this->qpdf); + names.replaceKey("/EmbeddedFiles", nth.getObjectHandle()); + this->m->embedded_files = + std::make_shared(nth); + } +} + +std::shared_ptr +QPDFEmbeddedFileDocumentHelper::getEmbeddedFile(std::string const& name) +{ + std::shared_ptr result; + if (this->m->embedded_files) + { + auto i = this->m->embedded_files->find(name); + if (i != this->m->embedded_files->end()) + { + result = std::make_shared(i->second); + } + } + return result; +} + +std::map> +QPDFEmbeddedFileDocumentHelper::getEmbeddedFiles() +{ + std::map> result; + if (this->m->embedded_files) + { + for (auto const& i: *(this->m->embedded_files)) + { + result[i.first] = std::make_shared( + i.second); + } + } + return result; +} + +void +QPDFEmbeddedFileDocumentHelper::replaceEmbeddedFile( + std::string const& name, QPDFFileSpecObjectHelper const& fs) +{ + initEmbeddedFiles(); + this->m->embedded_files->insert( + name, fs.getObjectHandle()); +} + +bool +QPDFEmbeddedFileDocumentHelper::removeEmbeddedFile(std::string const& name) +{ + if (! hasEmbeddedFiles()) + { + return false; + } + auto iter = this->m->embedded_files->find(name); + if (iter == this->m->embedded_files->end()) + { + return false; + } + auto oh = iter->second; + iter.remove(); + if (oh.isIndirect()) + { + this->qpdf.replaceObject(oh.getObjGen(), QPDFObjectHandle::newNull()); + } + + return true; +} diff --git a/libqpdf/QPDFFileSpecObjectHelper.cc b/libqpdf/QPDFFileSpecObjectHelper.cc new file mode 100644 index 00000000..ad422d2b --- /dev/null +++ b/libqpdf/QPDFFileSpecObjectHelper.cc @@ -0,0 +1,157 @@ +#include +#include +#include +#include + +#include +#include + +QPDFFileSpecObjectHelper::QPDFFileSpecObjectHelper( + QPDFObjectHandle oh) : + QPDFObjectHelper(oh) +{ + if (! oh.isDictionary()) + { + oh.warnIfPossible("Embedded file object is not a dictionary"); + return; + } + auto type = oh.getKey("/Type"); + if (! (type.isName() && (type.getName() == "/Filespec"))) + { + oh.warnIfPossible("Embedded file object's type is not /Filespec"); + } +} + +QPDFFileSpecObjectHelper::Members::Members() +{ +} + +static std::vector name_keys = { + "/UF", "/F", "/Unix", "/DOS", "/Mac"}; + +std::string +QPDFFileSpecObjectHelper::getDescription() +{ + std::string result; + auto desc = this->oh.getKey("/Desc"); + if (desc.isString()) + { + result = desc.getUTF8Value(); + } + return result; +} + +std::string +QPDFFileSpecObjectHelper::getFilename() +{ + for (auto const& i: name_keys) + { + auto k = this->oh.getKey(i); + if (k.isString()) + { + return k.getUTF8Value(); + } + } + return ""; +} + +std::map +QPDFFileSpecObjectHelper::getFilenames() +{ + std::map result; + for (auto const& i: name_keys) + { + auto k = this->oh.getKey(i); + if (k.isString()) + { + result[i] = k.getUTF8Value(); + } + } + return result; +} + +QPDFObjectHandle +QPDFFileSpecObjectHelper::getEmbeddedFileStream(std::string const& key) +{ + auto ef = this->oh.getKey("/EF"); + if (! ef.isDictionary()) + { + return QPDFObjectHandle::newNull(); + } + if (! key.empty()) + { + return ef.getKey(key); + } + for (auto const& i: name_keys) + { + auto k = ef.getKey(i); + if (k.isStream()) + { + return k; + } + } + return QPDFObjectHandle::newNull(); +} + +QPDFObjectHandle +QPDFFileSpecObjectHelper::getEmbeddedFileStreams() +{ + return this->oh.getKey("/EF"); +} + +QPDFFileSpecObjectHelper +QPDFFileSpecObjectHelper::createFileSpec( + QPDF& qpdf, + std::string const& filename, + std::string const& fullpath) +{ + return createFileSpec( + qpdf, filename, + QPDFEFStreamObjectHelper::createEFStream( + qpdf, + QUtil::file_provider(fullpath))); +} + +QPDFFileSpecObjectHelper +QPDFFileSpecObjectHelper::createFileSpec( + QPDF& qpdf, + std::string const& filename, + QPDFEFStreamObjectHelper efsoh) +{ + auto oh = qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary()); + oh.replaceKey("/Type", QPDFObjectHandle::newName("/Filespec")); + QPDFFileSpecObjectHelper result(oh); + result.setFilename(filename); + auto ef = QPDFObjectHandle::newDictionary(); + ef.replaceKey("/F", efsoh.getObjectHandle()); + ef.replaceKey("/UF", efsoh.getObjectHandle()); + oh.replaceKey("/EF", ef); + return result; +} + +QPDFFileSpecObjectHelper& +QPDFFileSpecObjectHelper::setDescription(std::string const& desc) +{ + this->oh.replaceKey("/Desc", QPDFObjectHandle::newUnicodeString(desc)); + return *this; +} + +QPDFFileSpecObjectHelper& +QPDFFileSpecObjectHelper::setFilename( + std::string const& unicode_name, + std::string const& compat_name) +{ + auto uf = QPDFObjectHandle::newUnicodeString(unicode_name); + this->oh.replaceKey("/UF", uf); + if (compat_name.empty()) + { + QTC::TC("qpdf", "QPDFFileSpecObjectHelper empty compat_name"); + this->oh.replaceKey("/F", uf); + } + else + { + QTC::TC("qpdf", "QPDFFileSpecObjectHelper non-empty compat_name"); + this->oh.replaceKey("/F", QPDFObjectHandle::newString(compat_name)); + } + return *this; +} diff --git a/libqpdf/build.mk b/libqpdf/build.mk index ca15611a..f453e58e 100644 --- a/libqpdf/build.mk +++ b/libqpdf/build.mk @@ -58,7 +58,10 @@ SRCS_libqpdf = \ libqpdf/QPDFAcroFormDocumentHelper.cc \ libqpdf/QPDFAnnotationObjectHelper.cc \ libqpdf/QPDFCryptoProvider.cc \ + libqpdf/QPDFEFStreamObjectHelper.cc \ + libqpdf/QPDFEmbeddedFileDocumentHelper.cc \ libqpdf/QPDFExc.cc \ + libqpdf/QPDFFileSpecObjectHelper.cc \ libqpdf/QPDFFormFieldObjectHelper.cc \ libqpdf/QPDFMatrix.cc \ libqpdf/QPDFNameTreeObjectHelper.cc \ -- cgit v1.2.3-70-g09d2