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,6 +4,12 @@ | ||
| 4 | pdf_time_to_qpdf_time, qpdf_time_to_pdf_time, | 4 | pdf_time_to_qpdf_time, qpdf_time_to_pdf_time, |
| 5 | get_current_qpdf_time. | 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 | 2021-02-07 Jay Berkenbilt <ejb@ql.org> | 13 | 2021-02-07 Jay Berkenbilt <ejb@ql.org> |
| 8 | 14 | ||
| 9 | * Add new functions QUtil::pipe_file and QUtil::file_provider for | 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,7 +58,10 @@ SRCS_libqpdf = \ | ||
| 58 | libqpdf/QPDFAcroFormDocumentHelper.cc \ | 58 | libqpdf/QPDFAcroFormDocumentHelper.cc \ |
| 59 | libqpdf/QPDFAnnotationObjectHelper.cc \ | 59 | libqpdf/QPDFAnnotationObjectHelper.cc \ |
| 60 | libqpdf/QPDFCryptoProvider.cc \ | 60 | libqpdf/QPDFCryptoProvider.cc \ |
| 61 | + libqpdf/QPDFEFStreamObjectHelper.cc \ | ||
| 62 | + libqpdf/QPDFEmbeddedFileDocumentHelper.cc \ | ||
| 61 | libqpdf/QPDFExc.cc \ | 63 | libqpdf/QPDFExc.cc \ |
| 64 | + libqpdf/QPDFFileSpecObjectHelper.cc \ | ||
| 62 | libqpdf/QPDFFormFieldObjectHelper.cc \ | 65 | libqpdf/QPDFFormFieldObjectHelper.cc \ |
| 63 | libqpdf/QPDFMatrix.cc \ | 66 | libqpdf/QPDFMatrix.cc \ |
| 64 | libqpdf/QPDFNameTreeObjectHelper.cc \ | 67 | libqpdf/QPDFNameTreeObjectHelper.cc \ |
manual/qpdf-manual.xml
| @@ -4951,6 +4951,16 @@ print "\n"; | @@ -4951,6 +4951,16 @@ print "\n"; | ||
| 4951 | </listitem> | 4951 | </listitem> |
| 4952 | <listitem> | 4952 | <listitem> |
| 4953 | <para> | 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 | Add <function>warn</function> to | 4964 | Add <function>warn</function> to |
| 4955 | <classname>QPDF</classname>'s public API. | 4965 | <classname>QPDF</classname>'s public API. |
| 4956 | </para> | 4966 | </para> |
qpdf/qpdf.testcov
| @@ -569,3 +569,5 @@ QPDFPageObjectHelper unresolved names 0 | @@ -569,3 +569,5 @@ QPDFPageObjectHelper unresolved names 0 | ||
| 569 | QPDFPageObjectHelper resolving unresolved 0 | 569 | QPDFPageObjectHelper resolving unresolved 0 |
| 570 | qpdf password stdin 0 | 570 | qpdf password stdin 0 |
| 571 | qpdf password file 0 | 571 | qpdf password file 0 |
| 572 | +QPDFFileSpecObjectHelper empty compat_name 0 | ||
| 573 | +QPDFFileSpecObjectHelper non-empty compat_name 0 |
qpdf/qtest/qpdf.test
| @@ -522,6 +522,29 @@ $td->runtest("page operations on form xobject", | @@ -522,6 +522,29 @@ $td->runtest("page operations on form xobject", | ||
| 522 | 522 | ||
| 523 | show_ntests(); | 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 | $td->notify("--- Stream Replacement Tests ---"); | 548 | $td->notify("--- Stream Replacement Tests ---"); |
| 526 | $n_tests += 8; | 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,6 +10,7 @@ | ||
| 10 | #include <qpdf/QPDFNameTreeObjectHelper.hh> | 10 | #include <qpdf/QPDFNameTreeObjectHelper.hh> |
| 11 | #include <qpdf/QPDFPageLabelDocumentHelper.hh> | 11 | #include <qpdf/QPDFPageLabelDocumentHelper.hh> |
| 12 | #include <qpdf/QPDFOutlineDocumentHelper.hh> | 12 | #include <qpdf/QPDFOutlineDocumentHelper.hh> |
| 13 | +#include <qpdf/QPDFEmbeddedFileDocumentHelper.hh> | ||
| 13 | #include <qpdf/QUtil.hh> | 14 | #include <qpdf/QUtil.hh> |
| 14 | #include <qpdf/QTC.hh> | 15 | #include <qpdf/QTC.hh> |
| 15 | #include <qpdf/Pl_StdioFile.hh> | 16 | #include <qpdf/Pl_StdioFile.hh> |
| @@ -2716,6 +2717,67 @@ void runtest(int n, char const* filename1, char const* arg2) | @@ -2716,6 +2717,67 @@ void runtest(int n, char const* filename1, char const* arg2) | ||
| 2716 | w.setQDFMode(true); | 2717 | w.setQDFMode(true); |
| 2717 | w.write(); | 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 | else | 2781 | else |
| 2720 | { | 2782 | { |
| 2721 | throw std::runtime_error(std::string("invalid test ") + | 2783 | throw std::runtime_error(std::string("invalid test ") + |