Commit ad34b9c278608dfdcfdbe7402acb3a6dd04c3d0e
1 parent
bf0e6eb3
Implement helpers for file attachments
Showing
15 changed files
with
1378 additions
and
0 deletions
ChangeLog
| ... | ... | @@ -4,6 +4,12 @@ |
| 4 | 4 | pdf_time_to_qpdf_time, qpdf_time_to_pdf_time, |
| 5 | 5 | get_current_qpdf_time. |
| 6 | 6 | |
| 7 | +2021-02-08 Jay Berkenbilt <ejb@ql.org> | |
| 8 | + | |
| 9 | + * Add helper classes for file attachments: | |
| 10 | + QPDFEmbeddedFileDocumentHelper, QPDFFileSpecObjectHelper, | |
| 11 | + QPDFEFStreamObjectHelper. See their header files for details. | |
| 12 | + | |
| 7 | 13 | 2021-02-07 Jay Berkenbilt <ejb@ql.org> |
| 8 | 14 | |
| 9 | 15 | * Add new functions QUtil::pipe_file and QUtil::file_provider for | ... | ... |
include/qpdf/QPDFEFStreamObjectHelper.hh
0 → 100644
| 1 | +// Copyright (c) 2005-2021 Jay Berkenbilt | |
| 2 | +// | |
| 3 | +// This file is part of qpdf. | |
| 4 | +// | |
| 5 | +// Licensed under the Apache License, Version 2.0 (the "License"); | |
| 6 | +// you may not use this file except in compliance with the License. | |
| 7 | +// You may obtain a copy of the License at | |
| 8 | +// | |
| 9 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 10 | +// | |
| 11 | +// Unless required by applicable law or agreed to in writing, software | |
| 12 | +// distributed under the License is distributed on an "AS IS" BASIS, | |
| 13 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 14 | +// See the License for the specific language governing permissions and | |
| 15 | +// limitations under the License. | |
| 16 | +// | |
| 17 | +// Versions of qpdf prior to version 7 were released under the terms | |
| 18 | +// of version 2.0 of the Artistic License. At your option, you may | |
| 19 | +// continue to consider qpdf to be licensed under those terms. Please | |
| 20 | +// see the manual for additional information. | |
| 21 | + | |
| 22 | +#ifndef QPDFEFSTREAMOBJECTHELPER_HH | |
| 23 | +#define QPDFEFSTREAMOBJECTHELPER_HH | |
| 24 | + | |
| 25 | +#include <qpdf/QPDFObjectHelper.hh> | |
| 26 | + | |
| 27 | +#include <qpdf/DLL.h> | |
| 28 | + | |
| 29 | +#include <qpdf/QPDFObjectHandle.hh> | |
| 30 | +#include <functional> | |
| 31 | + | |
| 32 | +// This class provides a higher level interface around Embedded File | |
| 33 | +// Streams, which are discussed in section 7.11.4 of the ISO-32000 PDF | |
| 34 | +// specification. | |
| 35 | + | |
| 36 | +class QPDFEFStreamObjectHelper: public QPDFObjectHelper | |
| 37 | +{ | |
| 38 | + public: | |
| 39 | + QPDF_DLL | |
| 40 | + QPDFEFStreamObjectHelper(QPDFObjectHandle); | |
| 41 | + QPDF_DLL | |
| 42 | + virtual ~QPDFEFStreamObjectHelper() = default; | |
| 43 | + | |
| 44 | + // Date parameters are strings that comform to the PDF spec for | |
| 45 | + // date/time strings, which is "D:yyyymmddhhmmss<z>" where <z> is | |
| 46 | + // either "Z" for UTC or "-hh'mm'" or "+hh'mm'" for timezone | |
| 47 | + // offset. Examples: "D:20210207161528-05'00'", | |
| 48 | + // "D:20210207211528Z". See QUtil::qpdf_time_to_pdf_time. | |
| 49 | + | |
| 50 | + QPDF_DLL | |
| 51 | + std::string getCreationDate(); | |
| 52 | + QPDF_DLL | |
| 53 | + std::string getModDate(); | |
| 54 | + // Get size as reported in the object; return 0 if not present. | |
| 55 | + QPDF_DLL | |
| 56 | + size_t getSize(); | |
| 57 | + // Subtype is a mime type such as "text/plain" | |
| 58 | + QPDF_DLL | |
| 59 | + std::string getSubtype(); | |
| 60 | + // Return the MD5 checksum as stored in the object as a binary | |
| 61 | + // string. This does not check consistency with the data. If not | |
| 62 | + // present, return an empty string. | |
| 63 | + QPDF_DLL | |
| 64 | + std::string getChecksum(); | |
| 65 | + | |
| 66 | + // Setters return a reference to this object so that they can be | |
| 67 | + // used as fluent interfaces, e.g. | |
| 68 | + // efsoh.setCreationDate(x).setModDate(y); | |
| 69 | + | |
| 70 | + // Create a new embedded file stream with the given stream data, | |
| 71 | + // which can be provided in any of several ways. To get the new | |
| 72 | + // object back, call getObjectHandle() on the returned object. The | |
| 73 | + // checksum and size are computed automatically and stored. Other | |
| 74 | + // parameters may be supplied using setters defined below. | |
| 75 | + QPDF_DLL | |
| 76 | + static QPDFEFStreamObjectHelper | |
| 77 | + createEFStream(QPDF& qpdf, PointerHolder<Buffer> data); | |
| 78 | + QPDF_DLL | |
| 79 | + static QPDFEFStreamObjectHelper | |
| 80 | + createEFStream(QPDF& qpdf, std::string const& data); | |
| 81 | + // The provider function must write the data to the given | |
| 82 | + // pipeline. The function may be called multiple times by the qpdf | |
| 83 | + // library. You can pass QUtil::file_provider(filename) as the | |
| 84 | + // provider to have the qpdf library provide the contents of | |
| 85 | + // filename as a binary. | |
| 86 | + QPDF_DLL | |
| 87 | + static QPDFEFStreamObjectHelper | |
| 88 | + createEFStream(QPDF& qpdf, std::function<void(Pipeline*)> provider); | |
| 89 | + | |
| 90 | + // Setters for other parameters | |
| 91 | + QPDF_DLL | |
| 92 | + QPDFEFStreamObjectHelper& setCreationDate(std::string const&); | |
| 93 | + QPDF_DLL | |
| 94 | + QPDFEFStreamObjectHelper& setModDate(std::string const&); | |
| 95 | + | |
| 96 | + // Set subtype as a mime-type, e.g. "text/plain" or | |
| 97 | + // "application/pdf". | |
| 98 | + QPDF_DLL | |
| 99 | + QPDFEFStreamObjectHelper& setSubtype(std::string const&); | |
| 100 | + | |
| 101 | + private: | |
| 102 | + QPDFObjectHandle getParam(std::string const& pkey); | |
| 103 | + void setParam(std::string const& pkey, QPDFObjectHandle const&); | |
| 104 | + static QPDFEFStreamObjectHelper newFromStream(QPDFObjectHandle stream); | |
| 105 | + | |
| 106 | + class Members | |
| 107 | + { | |
| 108 | + friend class QPDFEFStreamObjectHelper; | |
| 109 | + | |
| 110 | + public: | |
| 111 | + QPDF_DLL | |
| 112 | + ~Members() = default; | |
| 113 | + | |
| 114 | + private: | |
| 115 | + Members(); | |
| 116 | + Members(Members const&) = delete; | |
| 117 | + }; | |
| 118 | + | |
| 119 | + PointerHolder<Members> m; | |
| 120 | +}; | |
| 121 | + | |
| 122 | +#endif // QPDFEFSTREAMOBJECTHELPER_HH | ... | ... |
include/qpdf/QPDFEmbeddedFileDocumentHelper.hh
0 → 100644
| 1 | +// Copyright (c) 2005-2021 Jay Berkenbilt | |
| 2 | +// | |
| 3 | +// This file is part of qpdf. | |
| 4 | +// | |
| 5 | +// Licensed under the Apache License, Version 2.0 (the "License"); | |
| 6 | +// you may not use this file except in compliance with the License. | |
| 7 | +// You may obtain a copy of the License at | |
| 8 | +// | |
| 9 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 10 | +// | |
| 11 | +// Unless required by applicable law or agreed to in writing, software | |
| 12 | +// distributed under the License is distributed on an "AS IS" BASIS, | |
| 13 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 14 | +// See the License for the specific language governing permissions and | |
| 15 | +// limitations under the License. | |
| 16 | +// | |
| 17 | +// Versions of qpdf prior to version 7 were released under the terms | |
| 18 | +// of version 2.0 of the Artistic License. At your option, you may | |
| 19 | +// continue to consider qpdf to be licensed under those terms. Please | |
| 20 | +// see the manual for additional information. | |
| 21 | + | |
| 22 | +#ifndef QPDFEMBEDDEDFILEDOCUMENTHELPER_HH | |
| 23 | +#define QPDFEMBEDDEDFILEDOCUMENTHELPER_HH | |
| 24 | + | |
| 25 | +#include <qpdf/QPDFDocumentHelper.hh> | |
| 26 | + | |
| 27 | +#include <qpdf/QPDF.hh> | |
| 28 | +#include <qpdf/QPDFNameTreeObjectHelper.hh> | |
| 29 | +#include <qpdf/QPDFFileSpecObjectHelper.hh> | |
| 30 | +#include <qpdf/DLL.h> | |
| 31 | + | |
| 32 | +#include <memory> | |
| 33 | +#include <map> | |
| 34 | + | |
| 35 | +// This class provides a higher level interface around document-level | |
| 36 | +// file attachments, also known as embedded files. These are discussed | |
| 37 | +// in sections 7.7.4 and 7.11 of the ISO-32000 PDF specification. | |
| 38 | + | |
| 39 | +class QPDFEmbeddedFileDocumentHelper: public QPDFDocumentHelper | |
| 40 | +{ | |
| 41 | + public: | |
| 42 | + QPDF_DLL | |
| 43 | + QPDFEmbeddedFileDocumentHelper(QPDF&); | |
| 44 | + QPDF_DLL | |
| 45 | + virtual ~QPDFEmbeddedFileDocumentHelper() = default; | |
| 46 | + | |
| 47 | + QPDF_DLL | |
| 48 | + bool hasEmbeddedFiles() const; | |
| 49 | + | |
| 50 | + QPDF_DLL | |
| 51 | + std::map<std::string, | |
| 52 | + std::shared_ptr<QPDFFileSpecObjectHelper>> getEmbeddedFiles(); | |
| 53 | + | |
| 54 | + // If an embedded file with the given name exists, return a | |
| 55 | + // (shared) pointer to it. Otherwise, return nullptr. | |
| 56 | + QPDF_DLL | |
| 57 | + std::shared_ptr<QPDFFileSpecObjectHelper> | |
| 58 | + getEmbeddedFile(std::string const& name); | |
| 59 | + | |
| 60 | + // Add or replace an attachment | |
| 61 | + QPDF_DLL | |
| 62 | + void replaceEmbeddedFile( | |
| 63 | + std::string const& name, QPDFFileSpecObjectHelper const&); | |
| 64 | + | |
| 65 | + // Remove an embedded file if present. Return value is true if the | |
| 66 | + // file was present and was removed. This method not only removes | |
| 67 | + // the embedded file from the embedded files name tree but also | |
| 68 | + // nulls out the file specification dictionary. This means that | |
| 69 | + // any references to this file from file attachment annotations | |
| 70 | + // will also stop working. This is the best way to make the | |
| 71 | + // attachment actually disappear from the file and not just from | |
| 72 | + // the list of attachments. | |
| 73 | + QPDF_DLL | |
| 74 | + bool removeEmbeddedFile(std::string const& name); | |
| 75 | + | |
| 76 | + private: | |
| 77 | + void initEmbeddedFiles(); | |
| 78 | + | |
| 79 | + class Members | |
| 80 | + { | |
| 81 | + friend class QPDFEmbeddedFileDocumentHelper; | |
| 82 | + | |
| 83 | + public: | |
| 84 | + QPDF_DLL | |
| 85 | + ~Members() = default; | |
| 86 | + | |
| 87 | + private: | |
| 88 | + Members(); | |
| 89 | + Members(Members const&) = delete; | |
| 90 | + | |
| 91 | + std::shared_ptr<QPDFNameTreeObjectHelper> embedded_files; | |
| 92 | + }; | |
| 93 | + | |
| 94 | + PointerHolder<Members> m; | |
| 95 | +}; | |
| 96 | + | |
| 97 | +#endif // QPDFEMBEDDEDFILEDOCUMENTHELPER_HH | ... | ... |
include/qpdf/QPDFFileSpecObjectHelper.hh
0 → 100644
| 1 | +// Copyright (c) 2005-2021 Jay Berkenbilt | |
| 2 | +// | |
| 3 | +// This file is part of qpdf. | |
| 4 | +// | |
| 5 | +// Licensed under the Apache License, Version 2.0 (the "License"); | |
| 6 | +// you may not use this file except in compliance with the License. | |
| 7 | +// You may obtain a copy of the License at | |
| 8 | +// | |
| 9 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 10 | +// | |
| 11 | +// Unless required by applicable law or agreed to in writing, software | |
| 12 | +// distributed under the License is distributed on an "AS IS" BASIS, | |
| 13 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 14 | +// See the License for the specific language governing permissions and | |
| 15 | +// limitations under the License. | |
| 16 | +// | |
| 17 | +// Versions of qpdf prior to version 7 were released under the terms | |
| 18 | +// of version 2.0 of the Artistic License. At your option, you may | |
| 19 | +// continue to consider qpdf to be licensed under those terms. Please | |
| 20 | +// see the manual for additional information. | |
| 21 | + | |
| 22 | +#ifndef QPDFFILESPECOBJECTHELPER_HH | |
| 23 | +#define QPDFFILESPECOBJECTHELPER_HH | |
| 24 | + | |
| 25 | +#include <qpdf/QPDFObjectHelper.hh> | |
| 26 | + | |
| 27 | +#include <qpdf/DLL.h> | |
| 28 | + | |
| 29 | +#include <qpdf/QPDFObjectHandle.hh> | |
| 30 | +#include <qpdf/QPDFEFStreamObjectHelper.hh> | |
| 31 | + | |
| 32 | +// This class provides a higher level interface around File | |
| 33 | +// Specification dictionaries, which are discussed in section 7.11 of | |
| 34 | +// the ISO-32000 PDF specification. | |
| 35 | + | |
| 36 | +class QPDFFileSpecObjectHelper: public QPDFObjectHelper | |
| 37 | +{ | |
| 38 | + public: | |
| 39 | + QPDF_DLL | |
| 40 | + QPDFFileSpecObjectHelper(QPDFObjectHandle); | |
| 41 | + QPDF_DLL | |
| 42 | + virtual ~QPDFFileSpecObjectHelper() = default; | |
| 43 | + | |
| 44 | + QPDF_DLL | |
| 45 | + std::string getDescription(); | |
| 46 | + | |
| 47 | + // Get the main filename for this file specification. In priority | |
| 48 | + // order, check /UF, /F, /Unix, /DOS, /Mac. | |
| 49 | + QPDF_DLL | |
| 50 | + std::string getFilename(); | |
| 51 | + | |
| 52 | + // Return any of /UF, /F, /Unix, /DOS, /Mac filename keys that may | |
| 53 | + // be present in the object. | |
| 54 | + QPDF_DLL | |
| 55 | + std::map<std::string, std::string> getFilenames(); | |
| 56 | + | |
| 57 | + // Get the requested embedded file stream for this file | |
| 58 | + // specification. If key is empty, In priority order, check /UF, | |
| 59 | + // /F, /Unix, /DOS, /Mac. Returns a null object if not found. If | |
| 60 | + // this is an actual embedded file stream, its data is the content | |
| 61 | + // of the attachment. You can also use | |
| 62 | + // QPDFEFStreamObjectHelper for higher level access to | |
| 63 | + // the parameters. | |
| 64 | + QPDF_DLL | |
| 65 | + QPDFObjectHandle getEmbeddedFileStream(std::string const& key = ""); | |
| 66 | + | |
| 67 | + // Return the /EF key of the file spec, which is a map from file | |
| 68 | + // name key to embedded file stream. | |
| 69 | + QPDF_DLL | |
| 70 | + QPDFObjectHandle getEmbeddedFileStreams(); | |
| 71 | + | |
| 72 | + // Setters return a reference to this object so that they can be | |
| 73 | + // used as fluent interfaces, e.g. | |
| 74 | + // fsoh.setDescription(x).setFilename(y); | |
| 75 | + | |
| 76 | + // Create a new filespec as an indirect object with the given | |
| 77 | + // filename, and attach the contents of the specified file as data | |
| 78 | + // in an embedded file stream. | |
| 79 | + QPDF_DLL | |
| 80 | + static | |
| 81 | + QPDFFileSpecObjectHelper createFileSpec( | |
| 82 | + QPDF& qpdf, | |
| 83 | + std::string const& filename, | |
| 84 | + std::string const& fullpath); | |
| 85 | + | |
| 86 | + // Create a new filespec as an indirect object with the given | |
| 87 | + // unicode filename and embedded file stream. The file name will | |
| 88 | + // be used as both /UF and /F. If you need to override, call | |
| 89 | + // setFilename. | |
| 90 | + QPDF_DLL | |
| 91 | + static | |
| 92 | + QPDFFileSpecObjectHelper createFileSpec( | |
| 93 | + QPDF& qpdf, | |
| 94 | + std::string const& filename, | |
| 95 | + QPDFEFStreamObjectHelper); | |
| 96 | + | |
| 97 | + QPDF_DLL | |
| 98 | + QPDFFileSpecObjectHelper& setDescription(std::string const&); | |
| 99 | + // setFilename sets /UF to unicode_name. If compat_name is empty, | |
| 100 | + // it is also set to unicode_name. unicode_name should be a UTF-8 | |
| 101 | + // encoded string. compat_name is converted to a string | |
| 102 | + // QPDFObjectHandle literally, preserving whatever encoding it | |
| 103 | + // might happen to have. | |
| 104 | + QPDF_DLL | |
| 105 | + QPDFFileSpecObjectHelper& setFilename( | |
| 106 | + std::string const& unicode_name, | |
| 107 | + std::string const& compat_name = ""); | |
| 108 | + | |
| 109 | + private: | |
| 110 | + class Members | |
| 111 | + { | |
| 112 | + friend class QPDFFileSpecObjectHelper; | |
| 113 | + | |
| 114 | + public: | |
| 115 | + QPDF_DLL | |
| 116 | + ~Members() = default; | |
| 117 | + | |
| 118 | + private: | |
| 119 | + Members(); | |
| 120 | + Members(Members const&) = delete; | |
| 121 | + }; | |
| 122 | + | |
| 123 | + PointerHolder<Members> m; | |
| 124 | +}; | |
| 125 | + | |
| 126 | +#endif // QPDFFILESPECOBJECTHELPER_HH | ... | ... |
libqpdf/QPDFEFStreamObjectHelper.cc
0 → 100644
| 1 | +#include <qpdf/QPDFEFStreamObjectHelper.hh> | |
| 2 | +#include <qpdf/QIntC.hh> | |
| 3 | +#include <qpdf/QUtil.hh> | |
| 4 | +#include <qpdf/Pl_Count.hh> | |
| 5 | +#include <qpdf/Pl_MD5.hh> | |
| 6 | +#include <qpdf/Pl_Discard.hh> | |
| 7 | + | |
| 8 | +QPDFEFStreamObjectHelper::QPDFEFStreamObjectHelper( | |
| 9 | + QPDFObjectHandle oh) : | |
| 10 | + QPDFObjectHelper(oh), | |
| 11 | + m(new Members()) | |
| 12 | +{ | |
| 13 | +} | |
| 14 | + | |
| 15 | +QPDFEFStreamObjectHelper::Members::Members() | |
| 16 | +{ | |
| 17 | +} | |
| 18 | + | |
| 19 | +QPDFObjectHandle | |
| 20 | +QPDFEFStreamObjectHelper::getParam(std::string const& pkey) | |
| 21 | +{ | |
| 22 | + auto params = this->oh.getDict().getKey("/Params"); | |
| 23 | + if (params.isDictionary()) | |
| 24 | + { | |
| 25 | + return params.getKey(pkey); | |
| 26 | + } | |
| 27 | + return QPDFObjectHandle::newNull(); | |
| 28 | +} | |
| 29 | + | |
| 30 | +void | |
| 31 | +QPDFEFStreamObjectHelper::setParam( | |
| 32 | + std::string const& pkey, QPDFObjectHandle const& pval) | |
| 33 | +{ | |
| 34 | + auto params = this->oh.getDict().getKey("/Params"); | |
| 35 | + if (! params.isDictionary()) | |
| 36 | + { | |
| 37 | + params = QPDFObjectHandle::newDictionary(); | |
| 38 | + this->oh.getDict().replaceKey("/Params", params); | |
| 39 | + } | |
| 40 | + params.replaceKey(pkey, pval); | |
| 41 | +} | |
| 42 | + | |
| 43 | +std::string | |
| 44 | +QPDFEFStreamObjectHelper::getCreationDate() | |
| 45 | +{ | |
| 46 | + auto val = getParam("/CreationDate"); | |
| 47 | + if (val.isString()) | |
| 48 | + { | |
| 49 | + return val.getUTF8Value(); | |
| 50 | + } | |
| 51 | + return ""; | |
| 52 | +} | |
| 53 | + | |
| 54 | +std::string | |
| 55 | +QPDFEFStreamObjectHelper::getModDate() | |
| 56 | +{ | |
| 57 | + auto val = getParam("/ModDate"); | |
| 58 | + if (val.isString()) | |
| 59 | + { | |
| 60 | + return val.getUTF8Value(); | |
| 61 | + } | |
| 62 | + return ""; | |
| 63 | +} | |
| 64 | + | |
| 65 | +size_t | |
| 66 | +QPDFEFStreamObjectHelper::getSize() | |
| 67 | +{ | |
| 68 | + auto val = getParam("/Size"); | |
| 69 | + if (val.isInteger()) | |
| 70 | + { | |
| 71 | + return QIntC::to_size(val.getUIntValueAsUInt()); | |
| 72 | + } | |
| 73 | + return 0; | |
| 74 | +} | |
| 75 | + | |
| 76 | +std::string | |
| 77 | +QPDFEFStreamObjectHelper::getSubtype() | |
| 78 | +{ | |
| 79 | + auto val = getParam("/Subtype"); | |
| 80 | + if (val.isName()) | |
| 81 | + { | |
| 82 | + auto n = val.getName(); | |
| 83 | + if (n.length() > 1) | |
| 84 | + { | |
| 85 | + return n.substr(1); | |
| 86 | + } | |
| 87 | + } | |
| 88 | + return ""; | |
| 89 | +} | |
| 90 | + | |
| 91 | +std::string | |
| 92 | +QPDFEFStreamObjectHelper::getChecksum() | |
| 93 | +{ | |
| 94 | + auto val = getParam("/CheckSum"); | |
| 95 | + if (val.isString()) | |
| 96 | + { | |
| 97 | + return val.getStringValue(); | |
| 98 | + } | |
| 99 | + return ""; | |
| 100 | +} | |
| 101 | + | |
| 102 | +QPDFEFStreamObjectHelper | |
| 103 | +QPDFEFStreamObjectHelper::createEFStream( | |
| 104 | + QPDF& qpdf, PointerHolder<Buffer> data) | |
| 105 | +{ | |
| 106 | + return newFromStream(QPDFObjectHandle::newStream(&qpdf, data)); | |
| 107 | +} | |
| 108 | + | |
| 109 | +QPDFEFStreamObjectHelper | |
| 110 | +QPDFEFStreamObjectHelper::createEFStream( | |
| 111 | + QPDF& qpdf, std::string const& data) | |
| 112 | +{ | |
| 113 | + return newFromStream(QPDFObjectHandle::newStream(&qpdf, data)); | |
| 114 | +} | |
| 115 | + | |
| 116 | +namespace QEF | |
| 117 | +{ | |
| 118 | + class Provider: public QPDFObjectHandle::StreamDataProvider | |
| 119 | + { | |
| 120 | + public: | |
| 121 | + Provider(std::function<void(Pipeline*)> provider) : | |
| 122 | + StreamDataProvider(false), | |
| 123 | + provider(provider) | |
| 124 | + { | |
| 125 | + } | |
| 126 | + virtual ~Provider() = default; | |
| 127 | + virtual void provideStreamData(int objid, int generation, | |
| 128 | + Pipeline* pipeline) override | |
| 129 | + { | |
| 130 | + this->provider(pipeline); | |
| 131 | + } | |
| 132 | + | |
| 133 | + private: | |
| 134 | + std::function<void(Pipeline*)> provider; | |
| 135 | + }; | |
| 136 | +}; | |
| 137 | + | |
| 138 | +QPDFEFStreamObjectHelper | |
| 139 | +QPDFEFStreamObjectHelper::createEFStream( | |
| 140 | + QPDF& qpdf, std::function<void(Pipeline*)> provider) | |
| 141 | +{ | |
| 142 | + auto stream = QPDFObjectHandle::newStream(&qpdf); | |
| 143 | + stream.replaceStreamData(new QEF::Provider(provider), | |
| 144 | + QPDFObjectHandle::newNull(), | |
| 145 | + QPDFObjectHandle::newNull()); | |
| 146 | + return newFromStream(stream); | |
| 147 | +} | |
| 148 | + | |
| 149 | +QPDFEFStreamObjectHelper& | |
| 150 | +QPDFEFStreamObjectHelper::setCreationDate(std::string const& date) | |
| 151 | +{ | |
| 152 | + setParam("/CreationDate", QPDFObjectHandle::newString(date)); | |
| 153 | + return *this; | |
| 154 | +} | |
| 155 | + | |
| 156 | +QPDFEFStreamObjectHelper& | |
| 157 | +QPDFEFStreamObjectHelper::setModDate(std::string const& date) | |
| 158 | +{ | |
| 159 | + setParam("/ModDate", QPDFObjectHandle::newString(date)); | |
| 160 | + return *this; | |
| 161 | +} | |
| 162 | + | |
| 163 | +QPDFEFStreamObjectHelper& | |
| 164 | +QPDFEFStreamObjectHelper::setSubtype(std::string const& subtype) | |
| 165 | +{ | |
| 166 | + setParam("/Subtype", QPDFObjectHandle::newName("/" + subtype)); | |
| 167 | + return *this; | |
| 168 | +} | |
| 169 | + | |
| 170 | +QPDFEFStreamObjectHelper | |
| 171 | +QPDFEFStreamObjectHelper::newFromStream(QPDFObjectHandle stream) | |
| 172 | +{ | |
| 173 | + QPDFEFStreamObjectHelper result(stream); | |
| 174 | + stream.getDict().replaceKey( | |
| 175 | + "/Type", QPDFObjectHandle::newName("/EmbeddedFile")); | |
| 176 | + Pl_Discard discard; | |
| 177 | + Pl_MD5 md5("EF md5", &discard); | |
| 178 | + Pl_Count count("EF size", &md5); | |
| 179 | + if (! stream.pipeStreamData(&count, nullptr, 0, qpdf_dl_all)) | |
| 180 | + { | |
| 181 | + stream.warnIfPossible( | |
| 182 | + "unable to get stream data for new embedded file stream"); | |
| 183 | + } | |
| 184 | + else | |
| 185 | + { | |
| 186 | + result.setParam( | |
| 187 | + "/Size", QPDFObjectHandle::newInteger(count.getCount())); | |
| 188 | + result.setParam( | |
| 189 | + "/CheckSum", QPDFObjectHandle::newString( | |
| 190 | + QUtil::hex_decode(md5.getHexDigest()))); | |
| 191 | + } | |
| 192 | + return result; | |
| 193 | +} | ... | ... |
libqpdf/QPDFEmbeddedFileDocumentHelper.cc
0 → 100644
| 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 | + | |
| 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 | + | |
| 52 | +QPDFEmbeddedFileDocumentHelper::Members::Members() | |
| 53 | +{ | |
| 54 | +} | |
| 55 | + | |
| 56 | +bool | |
| 57 | +QPDFEmbeddedFileDocumentHelper::hasEmbeddedFiles() const | |
| 58 | +{ | |
| 59 | + return (this->m->embedded_files.get() != nullptr); | |
| 60 | +} | |
| 61 | + | |
| 62 | +void | |
| 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> | |
| 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>> | |
| 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 | |
| 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 | |
| 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 | +} | ... | ... |
libqpdf/QPDFFileSpecObjectHelper.cc
0 → 100644
| 1 | +#include <qpdf/QPDFFileSpecObjectHelper.hh> | |
| 2 | +#include <qpdf/QTC.hh> | |
| 3 | +#include <qpdf/QPDF.hh> | |
| 4 | +#include <qpdf/QUtil.hh> | |
| 5 | + | |
| 6 | +#include <vector> | |
| 7 | +#include <string> | |
| 8 | + | |
| 9 | +QPDFFileSpecObjectHelper::QPDFFileSpecObjectHelper( | |
| 10 | + QPDFObjectHandle oh) : | |
| 11 | + QPDFObjectHelper(oh) | |
| 12 | +{ | |
| 13 | + if (! oh.isDictionary()) | |
| 14 | + { | |
| 15 | + oh.warnIfPossible("Embedded file object is not a dictionary"); | |
| 16 | + return; | |
| 17 | + } | |
| 18 | + auto type = oh.getKey("/Type"); | |
| 19 | + if (! (type.isName() && (type.getName() == "/Filespec"))) | |
| 20 | + { | |
| 21 | + oh.warnIfPossible("Embedded file object's type is not /Filespec"); | |
| 22 | + } | |
| 23 | +} | |
| 24 | + | |
| 25 | +QPDFFileSpecObjectHelper::Members::Members() | |
| 26 | +{ | |
| 27 | +} | |
| 28 | + | |
| 29 | +static std::vector<std::string> name_keys = { | |
| 30 | + "/UF", "/F", "/Unix", "/DOS", "/Mac"}; | |
| 31 | + | |
| 32 | +std::string | |
| 33 | +QPDFFileSpecObjectHelper::getDescription() | |
| 34 | +{ | |
| 35 | + std::string result; | |
| 36 | + auto desc = this->oh.getKey("/Desc"); | |
| 37 | + if (desc.isString()) | |
| 38 | + { | |
| 39 | + result = desc.getUTF8Value(); | |
| 40 | + } | |
| 41 | + return result; | |
| 42 | +} | |
| 43 | + | |
| 44 | +std::string | |
| 45 | +QPDFFileSpecObjectHelper::getFilename() | |
| 46 | +{ | |
| 47 | + for (auto const& i: name_keys) | |
| 48 | + { | |
| 49 | + auto k = this->oh.getKey(i); | |
| 50 | + if (k.isString()) | |
| 51 | + { | |
| 52 | + return k.getUTF8Value(); | |
| 53 | + } | |
| 54 | + } | |
| 55 | + return ""; | |
| 56 | +} | |
| 57 | + | |
| 58 | +std::map<std::string, std::string> | |
| 59 | +QPDFFileSpecObjectHelper::getFilenames() | |
| 60 | +{ | |
| 61 | + std::map<std::string, std::string> result; | |
| 62 | + for (auto const& i: name_keys) | |
| 63 | + { | |
| 64 | + auto k = this->oh.getKey(i); | |
| 65 | + if (k.isString()) | |
| 66 | + { | |
| 67 | + result[i] = k.getUTF8Value(); | |
| 68 | + } | |
| 69 | + } | |
| 70 | + return result; | |
| 71 | +} | |
| 72 | + | |
| 73 | +QPDFObjectHandle | |
| 74 | +QPDFFileSpecObjectHelper::getEmbeddedFileStream(std::string const& key) | |
| 75 | +{ | |
| 76 | + auto ef = this->oh.getKey("/EF"); | |
| 77 | + if (! ef.isDictionary()) | |
| 78 | + { | |
| 79 | + return QPDFObjectHandle::newNull(); | |
| 80 | + } | |
| 81 | + if (! key.empty()) | |
| 82 | + { | |
| 83 | + return ef.getKey(key); | |
| 84 | + } | |
| 85 | + for (auto const& i: name_keys) | |
| 86 | + { | |
| 87 | + auto k = ef.getKey(i); | |
| 88 | + if (k.isStream()) | |
| 89 | + { | |
| 90 | + return k; | |
| 91 | + } | |
| 92 | + } | |
| 93 | + return QPDFObjectHandle::newNull(); | |
| 94 | +} | |
| 95 | + | |
| 96 | +QPDFObjectHandle | |
| 97 | +QPDFFileSpecObjectHelper::getEmbeddedFileStreams() | |
| 98 | +{ | |
| 99 | + return this->oh.getKey("/EF"); | |
| 100 | +} | |
| 101 | + | |
| 102 | +QPDFFileSpecObjectHelper | |
| 103 | +QPDFFileSpecObjectHelper::createFileSpec( | |
| 104 | + QPDF& qpdf, | |
| 105 | + std::string const& filename, | |
| 106 | + std::string const& fullpath) | |
| 107 | +{ | |
| 108 | + return createFileSpec( | |
| 109 | + qpdf, filename, | |
| 110 | + QPDFEFStreamObjectHelper::createEFStream( | |
| 111 | + qpdf, | |
| 112 | + QUtil::file_provider(fullpath))); | |
| 113 | +} | |
| 114 | + | |
| 115 | +QPDFFileSpecObjectHelper | |
| 116 | +QPDFFileSpecObjectHelper::createFileSpec( | |
| 117 | + QPDF& qpdf, | |
| 118 | + std::string const& filename, | |
| 119 | + QPDFEFStreamObjectHelper efsoh) | |
| 120 | +{ | |
| 121 | + auto oh = qpdf.makeIndirectObject(QPDFObjectHandle::newDictionary()); | |
| 122 | + oh.replaceKey("/Type", QPDFObjectHandle::newName("/Filespec")); | |
| 123 | + QPDFFileSpecObjectHelper result(oh); | |
| 124 | + result.setFilename(filename); | |
| 125 | + auto ef = QPDFObjectHandle::newDictionary(); | |
| 126 | + ef.replaceKey("/F", efsoh.getObjectHandle()); | |
| 127 | + ef.replaceKey("/UF", efsoh.getObjectHandle()); | |
| 128 | + oh.replaceKey("/EF", ef); | |
| 129 | + return result; | |
| 130 | +} | |
| 131 | + | |
| 132 | +QPDFFileSpecObjectHelper& | |
| 133 | +QPDFFileSpecObjectHelper::setDescription(std::string const& desc) | |
| 134 | +{ | |
| 135 | + this->oh.replaceKey("/Desc", QPDFObjectHandle::newUnicodeString(desc)); | |
| 136 | + return *this; | |
| 137 | +} | |
| 138 | + | |
| 139 | +QPDFFileSpecObjectHelper& | |
| 140 | +QPDFFileSpecObjectHelper::setFilename( | |
| 141 | + std::string const& unicode_name, | |
| 142 | + std::string const& compat_name) | |
| 143 | +{ | |
| 144 | + auto uf = QPDFObjectHandle::newUnicodeString(unicode_name); | |
| 145 | + this->oh.replaceKey("/UF", uf); | |
| 146 | + if (compat_name.empty()) | |
| 147 | + { | |
| 148 | + QTC::TC("qpdf", "QPDFFileSpecObjectHelper empty compat_name"); | |
| 149 | + this->oh.replaceKey("/F", uf); | |
| 150 | + } | |
| 151 | + else | |
| 152 | + { | |
| 153 | + QTC::TC("qpdf", "QPDFFileSpecObjectHelper non-empty compat_name"); | |
| 154 | + this->oh.replaceKey("/F", QPDFObjectHandle::newString(compat_name)); | |
| 155 | + } | |
| 156 | + return *this; | |
| 157 | +} | ... | ... |
libqpdf/build.mk
| ... | ... | @@ -58,7 +58,10 @@ SRCS_libqpdf = \ |
| 58 | 58 | libqpdf/QPDFAcroFormDocumentHelper.cc \ |
| 59 | 59 | libqpdf/QPDFAnnotationObjectHelper.cc \ |
| 60 | 60 | libqpdf/QPDFCryptoProvider.cc \ |
| 61 | + libqpdf/QPDFEFStreamObjectHelper.cc \ | |
| 62 | + libqpdf/QPDFEmbeddedFileDocumentHelper.cc \ | |
| 61 | 63 | libqpdf/QPDFExc.cc \ |
| 64 | + libqpdf/QPDFFileSpecObjectHelper.cc \ | |
| 62 | 65 | libqpdf/QPDFFormFieldObjectHelper.cc \ |
| 63 | 66 | libqpdf/QPDFMatrix.cc \ |
| 64 | 67 | libqpdf/QPDFNameTreeObjectHelper.cc \ | ... | ... |
manual/qpdf-manual.xml
| ... | ... | @@ -4951,6 +4951,16 @@ print "\n"; |
| 4951 | 4951 | </listitem> |
| 4952 | 4952 | <listitem> |
| 4953 | 4953 | <para> |
| 4954 | + Add new helper classes for supporting file attachments, also | |
| 4955 | + known as embedded files. New classes are | |
| 4956 | + <classname>QPDFEmbeddedFileDocumentHelper</classname>, | |
| 4957 | + <classname>QPDFFileSpecObjectHelper</classname>, and | |
| 4958 | + <classname>QPDFEFStreamObjectHelper</classname>. See their | |
| 4959 | + respective headers for details. | |
| 4960 | + </para> | |
| 4961 | + </listitem> | |
| 4962 | + <listitem> | |
| 4963 | + <para> | |
| 4954 | 4964 | Add <function>warn</function> to |
| 4955 | 4965 | <classname>QPDF</classname>'s public API. |
| 4956 | 4966 | </para> | ... | ... |
qpdf/qpdf.testcov
qpdf/qtest/qpdf.test
| ... | ... | @@ -522,6 +522,29 @@ $td->runtest("page operations on form xobject", |
| 522 | 522 | |
| 523 | 523 | show_ntests(); |
| 524 | 524 | # ---------- |
| 525 | +$td->notify("--- File Attachments ---"); | |
| 526 | +$n_tests += 4; | |
| 527 | + | |
| 528 | +open(F, ">auto-txt") or die; | |
| 529 | +print F "from file"; | |
| 530 | +close(F); | |
| 531 | +$td->runtest("attachments", | |
| 532 | + {$td->COMMAND => "test_driver 76 minimal.pdf auto-txt"}, | |
| 533 | + {$td->FILE => "test76.out", $td->EXIT_STATUS => 0}, | |
| 534 | + $td->NORMALIZE_NEWLINES); | |
| 535 | +$td->runtest("check output", | |
| 536 | + {$td->FILE => "a.pdf"}, | |
| 537 | + {$td->FILE => "test76.pdf"}); | |
| 538 | +$td->runtest("attachments", | |
| 539 | + {$td->COMMAND => "test_driver 77 test76.pdf"}, | |
| 540 | + {$td->STRING => "test 77 done\n", $td->EXIT_STATUS => 0}, | |
| 541 | + $td->NORMALIZE_NEWLINES); | |
| 542 | +$td->runtest("check output", | |
| 543 | + {$td->FILE => "a.pdf"}, | |
| 544 | + {$td->FILE => "test77.pdf"}); | |
| 545 | + | |
| 546 | +show_ntests(); | |
| 547 | +# ---------- | |
| 525 | 548 | $td->notify("--- Stream Replacement Tests ---"); |
| 526 | 549 | $n_tests += 8; |
| 527 | 550 | ... | ... |
qpdf/qtest/qpdf/test76.out
0 → 100644
qpdf/qtest/qpdf/test76.pdf
0 → 100644
| 1 | +%PDF-1.3 | |
| 2 | +%¿÷¢þ | |
| 3 | +%QDF-1.0 | |
| 4 | + | |
| 5 | +%% Original object ID: 1 0 | |
| 6 | +1 0 obj | |
| 7 | +<< | |
| 8 | + /Names << | |
| 9 | + /EmbeddedFiles 2 0 R | |
| 10 | + >> | |
| 11 | + /Pages 3 0 R | |
| 12 | + /Type /Catalog | |
| 13 | +>> | |
| 14 | +endobj | |
| 15 | + | |
| 16 | +%% Original object ID: 9 0 | |
| 17 | +2 0 obj | |
| 18 | +<< | |
| 19 | + /Names [ | |
| 20 | + (att1) | |
| 21 | + 4 0 R | |
| 22 | + (att2) | |
| 23 | + 5 0 R | |
| 24 | + (att3) | |
| 25 | + 6 0 R | |
| 26 | + ] | |
| 27 | +>> | |
| 28 | +endobj | |
| 29 | + | |
| 30 | +%% Original object ID: 2 0 | |
| 31 | +3 0 obj | |
| 32 | +<< | |
| 33 | + /Count 1 | |
| 34 | + /Kids [ | |
| 35 | + 7 0 R | |
| 36 | + ] | |
| 37 | + /Type /Pages | |
| 38 | +>> | |
| 39 | +endobj | |
| 40 | + | |
| 41 | +%% Original object ID: 8 0 | |
| 42 | +4 0 obj | |
| 43 | +<< | |
| 44 | + /Desc (some text) | |
| 45 | + /EF << | |
| 46 | + /F 8 0 R | |
| 47 | + /UF 8 0 R | |
| 48 | + >> | |
| 49 | + /F (att1.txt) | |
| 50 | + /Type /Filespec | |
| 51 | + /UF (att1.txt) | |
| 52 | +>> | |
| 53 | +endobj | |
| 54 | + | |
| 55 | +%% Original object ID: 12 0 | |
| 56 | +5 0 obj | |
| 57 | +<< | |
| 58 | + /EF << | |
| 59 | + /F 10 0 R | |
| 60 | + /UF 10 0 R | |
| 61 | + >> | |
| 62 | + /F (att2.txt) | |
| 63 | + /Type /Filespec | |
| 64 | + /UF (att2.txt) | |
| 65 | +>> | |
| 66 | +endobj | |
| 67 | + | |
| 68 | +%% Original object ID: 13 0 | |
| 69 | +6 0 obj | |
| 70 | +<< | |
| 71 | + /EF << | |
| 72 | + /F 12 0 R | |
| 73 | + /UF 12 0 R | |
| 74 | + >> | |
| 75 | + /F (att3.txt) | |
| 76 | + /Type /Filespec | |
| 77 | + /UF <feff03c0002e007400780074> | |
| 78 | +>> | |
| 79 | +endobj | |
| 80 | + | |
| 81 | +%% Page 1 | |
| 82 | +%% Original object ID: 3 0 | |
| 83 | +7 0 obj | |
| 84 | +<< | |
| 85 | + /Contents 14 0 R | |
| 86 | + /MediaBox [ | |
| 87 | + 0 | |
| 88 | + 0 | |
| 89 | + 612 | |
| 90 | + 792 | |
| 91 | + ] | |
| 92 | + /Parent 3 0 R | |
| 93 | + /Resources << | |
| 94 | + /Font << | |
| 95 | + /F1 16 0 R | |
| 96 | + >> | |
| 97 | + /ProcSet 17 0 R | |
| 98 | + >> | |
| 99 | + /Type /Page | |
| 100 | +>> | |
| 101 | +endobj | |
| 102 | + | |
| 103 | +%% Original object ID: 7 0 | |
| 104 | +8 0 obj | |
| 105 | +<< | |
| 106 | + /Params << | |
| 107 | + /CheckSum <2e10f186a4cdf5be438747f4bdc2d4d4> | |
| 108 | + /CreationDate (D:20210207191121-05'00') | |
| 109 | + /ModDate (D:20210208001122Z) | |
| 110 | + /Size 9 | |
| 111 | + /Subtype /text#2fplain | |
| 112 | + >> | |
| 113 | + /Type /EmbeddedFile | |
| 114 | + /Length 9 0 R | |
| 115 | +>> | |
| 116 | +stream | |
| 117 | +from file | |
| 118 | +endstream | |
| 119 | +endobj | |
| 120 | + | |
| 121 | +%QDF: ignore_newline | |
| 122 | +9 0 obj | |
| 123 | +9 | |
| 124 | +endobj | |
| 125 | + | |
| 126 | +%% Original object ID: 10 0 | |
| 127 | +10 0 obj | |
| 128 | +<< | |
| 129 | + /Params << | |
| 130 | + /CheckSum <2fce9c8228e360ba9b04a1bd1bf63d6b> | |
| 131 | + /Size 11 | |
| 132 | + /Subtype /text#2fplain | |
| 133 | + >> | |
| 134 | + /Type /EmbeddedFile | |
| 135 | + /Length 11 0 R | |
| 136 | +>> | |
| 137 | +stream | |
| 138 | +from string | |
| 139 | +endstream | |
| 140 | +endobj | |
| 141 | + | |
| 142 | +%QDF: ignore_newline | |
| 143 | +11 0 obj | |
| 144 | +11 | |
| 145 | +endobj | |
| 146 | + | |
| 147 | +%% Original object ID: 11 0 | |
| 148 | +12 0 obj | |
| 149 | +<< | |
| 150 | + /Params << | |
| 151 | + /CheckSum <2236c155b1d62b7f00285bba081d4336> | |
| 152 | + /Size 11 | |
| 153 | + /Subtype /text#2fplain | |
| 154 | + >> | |
| 155 | + /Type /EmbeddedFile | |
| 156 | + /Length 13 0 R | |
| 157 | +>> | |
| 158 | +stream | |
| 159 | +from buffer | |
| 160 | +endstream | |
| 161 | +endobj | |
| 162 | + | |
| 163 | +%QDF: ignore_newline | |
| 164 | +13 0 obj | |
| 165 | +11 | |
| 166 | +endobj | |
| 167 | + | |
| 168 | +%% Contents for page 1 | |
| 169 | +%% Original object ID: 4 0 | |
| 170 | +14 0 obj | |
| 171 | +<< | |
| 172 | + /Length 15 0 R | |
| 173 | +>> | |
| 174 | +stream | |
| 175 | +BT | |
| 176 | + /F1 24 Tf | |
| 177 | + 72 720 Td | |
| 178 | + (Potato) Tj | |
| 179 | +ET | |
| 180 | +endstream | |
| 181 | +endobj | |
| 182 | + | |
| 183 | +15 0 obj | |
| 184 | +44 | |
| 185 | +endobj | |
| 186 | + | |
| 187 | +%% Original object ID: 6 0 | |
| 188 | +16 0 obj | |
| 189 | +<< | |
| 190 | + /BaseFont /Helvetica | |
| 191 | + /Encoding /WinAnsiEncoding | |
| 192 | + /Name /F1 | |
| 193 | + /Subtype /Type1 | |
| 194 | + /Type /Font | |
| 195 | +>> | |
| 196 | +endobj | |
| 197 | + | |
| 198 | +%% Original object ID: 5 0 | |
| 199 | +17 0 obj | |
| 200 | +[ | |
| 201 | ||
| 202 | + /Text | |
| 203 | +] | |
| 204 | +endobj | |
| 205 | + | |
| 206 | +xref | |
| 207 | +0 18 | |
| 208 | +0000000000 65535 f | |
| 209 | +0000000052 00000 n | |
| 210 | +0000000175 00000 n | |
| 211 | +0000000302 00000 n | |
| 212 | +0000000401 00000 n | |
| 213 | +0000000563 00000 n | |
| 214 | +0000000707 00000 n | |
| 215 | +0000000876 00000 n | |
| 216 | +0000001098 00000 n | |
| 217 | +0000001389 00000 n | |
| 218 | +0000001435 00000 n | |
| 219 | +0000001654 00000 n | |
| 220 | +0000001702 00000 n | |
| 221 | +0000001921 00000 n | |
| 222 | +0000001991 00000 n | |
| 223 | +0000002092 00000 n | |
| 224 | +0000002139 00000 n | |
| 225 | +0000002285 00000 n | |
| 226 | +trailer << | |
| 227 | + /Root 1 0 R | |
| 228 | + /Size 18 | |
| 229 | + /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] | |
| 230 | +>> | |
| 231 | +startxref | |
| 232 | +2321 | |
| 233 | +%%EOF | ... | ... |
qpdf/qtest/qpdf/test77.pdf
0 → 100644
| 1 | +%PDF-1.3 | |
| 2 | +%¿÷¢þ | |
| 3 | +%QDF-1.0 | |
| 4 | + | |
| 5 | +%% Original object ID: 1 0 | |
| 6 | +1 0 obj | |
| 7 | +<< | |
| 8 | + /Names << | |
| 9 | + /EmbeddedFiles 2 0 R | |
| 10 | + >> | |
| 11 | + /Pages 3 0 R | |
| 12 | + /Type /Catalog | |
| 13 | +>> | |
| 14 | +endobj | |
| 15 | + | |
| 16 | +%% Original object ID: 2 0 | |
| 17 | +2 0 obj | |
| 18 | +<< | |
| 19 | + /Names [ | |
| 20 | + (att1) | |
| 21 | + 4 0 R | |
| 22 | + (att3) | |
| 23 | + 5 0 R | |
| 24 | + ] | |
| 25 | +>> | |
| 26 | +endobj | |
| 27 | + | |
| 28 | +%% Original object ID: 3 0 | |
| 29 | +3 0 obj | |
| 30 | +<< | |
| 31 | + /Count 1 | |
| 32 | + /Kids [ | |
| 33 | + 6 0 R | |
| 34 | + ] | |
| 35 | + /Type /Pages | |
| 36 | +>> | |
| 37 | +endobj | |
| 38 | + | |
| 39 | +%% Original object ID: 4 0 | |
| 40 | +4 0 obj | |
| 41 | +<< | |
| 42 | + /Desc (some text) | |
| 43 | + /EF << | |
| 44 | + /F 7 0 R | |
| 45 | + /UF 7 0 R | |
| 46 | + >> | |
| 47 | + /F (att1.txt) | |
| 48 | + /Type /Filespec | |
| 49 | + /UF (att1.txt) | |
| 50 | +>> | |
| 51 | +endobj | |
| 52 | + | |
| 53 | +%% Original object ID: 6 0 | |
| 54 | +5 0 obj | |
| 55 | +<< | |
| 56 | + /EF << | |
| 57 | + /F 9 0 R | |
| 58 | + /UF 9 0 R | |
| 59 | + >> | |
| 60 | + /F (att3.txt) | |
| 61 | + /Type /Filespec | |
| 62 | + /UF <feff03c0002e007400780074> | |
| 63 | +>> | |
| 64 | +endobj | |
| 65 | + | |
| 66 | +%% Page 1 | |
| 67 | +%% Original object ID: 7 0 | |
| 68 | +6 0 obj | |
| 69 | +<< | |
| 70 | + /Contents 11 0 R | |
| 71 | + /MediaBox [ | |
| 72 | + 0 | |
| 73 | + 0 | |
| 74 | + 612 | |
| 75 | + 792 | |
| 76 | + ] | |
| 77 | + /Parent 3 0 R | |
| 78 | + /Resources << | |
| 79 | + /Font << | |
| 80 | + /F1 13 0 R | |
| 81 | + >> | |
| 82 | + /ProcSet 14 0 R | |
| 83 | + >> | |
| 84 | + /Type /Page | |
| 85 | +>> | |
| 86 | +endobj | |
| 87 | + | |
| 88 | +%% Original object ID: 8 0 | |
| 89 | +7 0 obj | |
| 90 | +<< | |
| 91 | + /Params << | |
| 92 | + /CheckSum <2e10f186a4cdf5be438747f4bdc2d4d4> | |
| 93 | + /CreationDate (D:20210207191121-05'00') | |
| 94 | + /ModDate (D:20210208001122Z) | |
| 95 | + /Size 9 | |
| 96 | + /Subtype /text#2fplain | |
| 97 | + >> | |
| 98 | + /Type /EmbeddedFile | |
| 99 | + /Length 8 0 R | |
| 100 | +>> | |
| 101 | +stream | |
| 102 | +from file | |
| 103 | +endstream | |
| 104 | +endobj | |
| 105 | + | |
| 106 | +%QDF: ignore_newline | |
| 107 | +8 0 obj | |
| 108 | +9 | |
| 109 | +endobj | |
| 110 | + | |
| 111 | +%% Original object ID: 12 0 | |
| 112 | +9 0 obj | |
| 113 | +<< | |
| 114 | + /Params << | |
| 115 | + /CheckSum <2236c155b1d62b7f00285bba081d4336> | |
| 116 | + /Size 11 | |
| 117 | + /Subtype /text#2fplain | |
| 118 | + >> | |
| 119 | + /Type /EmbeddedFile | |
| 120 | + /Length 10 0 R | |
| 121 | +>> | |
| 122 | +stream | |
| 123 | +from buffer | |
| 124 | +endstream | |
| 125 | +endobj | |
| 126 | + | |
| 127 | +%QDF: ignore_newline | |
| 128 | +10 0 obj | |
| 129 | +11 | |
| 130 | +endobj | |
| 131 | + | |
| 132 | +%% Contents for page 1 | |
| 133 | +%% Original object ID: 14 0 | |
| 134 | +11 0 obj | |
| 135 | +<< | |
| 136 | + /Length 12 0 R | |
| 137 | +>> | |
| 138 | +stream | |
| 139 | +BT | |
| 140 | + /F1 24 Tf | |
| 141 | + 72 720 Td | |
| 142 | + (Potato) Tj | |
| 143 | +ET | |
| 144 | +endstream | |
| 145 | +endobj | |
| 146 | + | |
| 147 | +12 0 obj | |
| 148 | +44 | |
| 149 | +endobj | |
| 150 | + | |
| 151 | +%% Original object ID: 16 0 | |
| 152 | +13 0 obj | |
| 153 | +<< | |
| 154 | + /BaseFont /Helvetica | |
| 155 | + /Encoding /WinAnsiEncoding | |
| 156 | + /Name /F1 | |
| 157 | + /Subtype /Type1 | |
| 158 | + /Type /Font | |
| 159 | +>> | |
| 160 | +endobj | |
| 161 | + | |
| 162 | +%% Original object ID: 17 0 | |
| 163 | +14 0 obj | |
| 164 | +[ | |
| 165 | ||
| 166 | + /Text | |
| 167 | +] | |
| 168 | +endobj | |
| 169 | + | |
| 170 | +xref | |
| 171 | +0 15 | |
| 172 | +0000000000 65535 f | |
| 173 | +0000000052 00000 n | |
| 174 | +0000000175 00000 n | |
| 175 | +0000000281 00000 n | |
| 176 | +0000000380 00000 n | |
| 177 | +0000000541 00000 n | |
| 178 | +0000000708 00000 n | |
| 179 | +0000000930 00000 n | |
| 180 | +0000001221 00000 n | |
| 181 | +0000001267 00000 n | |
| 182 | +0000001485 00000 n | |
| 183 | +0000001556 00000 n | |
| 184 | +0000001657 00000 n | |
| 185 | +0000001705 00000 n | |
| 186 | +0000001852 00000 n | |
| 187 | +trailer << | |
| 188 | + /Root 1 0 R | |
| 189 | + /Size 15 | |
| 190 | + /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] | |
| 191 | +>> | |
| 192 | +startxref | |
| 193 | +1888 | |
| 194 | +%%EOF | ... | ... |
qpdf/test_driver.cc
| ... | ... | @@ -10,6 +10,7 @@ |
| 10 | 10 | #include <qpdf/QPDFNameTreeObjectHelper.hh> |
| 11 | 11 | #include <qpdf/QPDFPageLabelDocumentHelper.hh> |
| 12 | 12 | #include <qpdf/QPDFOutlineDocumentHelper.hh> |
| 13 | +#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> | |
| 13 | 14 | #include <qpdf/QUtil.hh> |
| 14 | 15 | #include <qpdf/QTC.hh> |
| 15 | 16 | #include <qpdf/Pl_StdioFile.hh> |
| ... | ... | @@ -2716,6 +2717,67 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 2716 | 2717 | w.setQDFMode(true); |
| 2717 | 2718 | w.write(); |
| 2718 | 2719 | } |
| 2720 | + else if (n == 76) | |
| 2721 | + { | |
| 2722 | + // Embedded files. arg2 is a file to attach. Hard-code the | |
| 2723 | + // mime type and file name for test purposes. | |
| 2724 | + QPDFEmbeddedFileDocumentHelper efdh(pdf); | |
| 2725 | + auto fs1 = QPDFFileSpecObjectHelper::createFileSpec( | |
| 2726 | + pdf, "att1.txt", arg2); | |
| 2727 | + fs1.setDescription("some text"); | |
| 2728 | + auto efs1 = QPDFEFStreamObjectHelper(fs1.getEmbeddedFileStream()); | |
| 2729 | + efs1.setSubtype("text/plain") | |
| 2730 | + .setCreationDate("D:20210207191121-05'00'") | |
| 2731 | + .setModDate("D:20210208001122Z"); | |
| 2732 | + efdh.replaceEmbeddedFile("att1", fs1); | |
| 2733 | + auto efs2 = QPDFEFStreamObjectHelper::createEFStream( | |
| 2734 | + pdf, "from string"); | |
| 2735 | + efs2.setSubtype("text/plain"); | |
| 2736 | + Pl_Buffer p("buffer"); | |
| 2737 | + p.write(QUtil::unsigned_char_pointer("from buffer"), 11); | |
| 2738 | + p.finish(); | |
| 2739 | + auto efs3 = QPDFEFStreamObjectHelper::createEFStream( | |
| 2740 | + pdf, p.getBuffer()); | |
| 2741 | + efs3.setSubtype("text/plain"); | |
| 2742 | + efdh.replaceEmbeddedFile( | |
| 2743 | + "att2", QPDFFileSpecObjectHelper::createFileSpec( | |
| 2744 | + pdf, "att2.txt", efs2)); | |
| 2745 | + auto fs3 = QPDFFileSpecObjectHelper::createFileSpec( | |
| 2746 | + pdf, "att3.txt", efs3); | |
| 2747 | + efdh.replaceEmbeddedFile("att3", fs3); | |
| 2748 | + fs3.setFilename("\xcf\x80.txt", "att3.txt"); | |
| 2749 | + | |
| 2750 | + assert(efs1.getCreationDate() == "D:20210207191121-05'00'"); | |
| 2751 | + assert(efs1.getModDate() == "D:20210208001122Z"); | |
| 2752 | + assert(efs2.getSize() == 11); | |
| 2753 | + assert(efs2.getSubtype() == "text/plain"); | |
| 2754 | + assert(QUtil::hex_encode(efs2.getChecksum()) == | |
| 2755 | + "2fce9c8228e360ba9b04a1bd1bf63d6b"); | |
| 2756 | + | |
| 2757 | + for (auto iter: efdh.getEmbeddedFiles()) | |
| 2758 | + { | |
| 2759 | + std::cout << iter.first << " -> " << iter.second->getFilename() | |
| 2760 | + << std::endl; | |
| 2761 | + } | |
| 2762 | + assert(efdh.getEmbeddedFile("att1")->getFilename() == "att1.txt"); | |
| 2763 | + assert(! efdh.getEmbeddedFile("potato")); | |
| 2764 | + | |
| 2765 | + QPDFWriter w(pdf, "a.pdf"); | |
| 2766 | + w.setStaticID(true); | |
| 2767 | + w.setQDFMode(true); | |
| 2768 | + w.write(); | |
| 2769 | + } | |
| 2770 | + else if (n == 77) | |
| 2771 | + { | |
| 2772 | + QPDFEmbeddedFileDocumentHelper efdh(pdf); | |
| 2773 | + assert(efdh.removeEmbeddedFile("att2")); | |
| 2774 | + assert(! efdh.removeEmbeddedFile("att2")); | |
| 2775 | + | |
| 2776 | + QPDFWriter w(pdf, "a.pdf"); | |
| 2777 | + w.setStaticID(true); | |
| 2778 | + w.setQDFMode(true); | |
| 2779 | + w.write(); | |
| 2780 | + } | |
| 2719 | 2781 | else |
| 2720 | 2782 | { |
| 2721 | 2783 | throw std::runtime_error(std::string("invalid test ") + | ... | ... |