Commit 93ac1695a4b79f3d5b71e2d57ed876c28866d2c9
1 parent
eff2c9a6
Support files with only attachments encrypted
Test cases added in a future commit since they depend on /R=6 support.
Showing
9 changed files
with
253 additions
and
179 deletions
TODO
| @@ -89,20 +89,11 @@ Index: QPDFWriter.cc | @@ -89,20 +89,11 @@ Index: QPDFWriter.cc | ||
| 89 | } | 89 | } |
| 90 | ------------------------------ | 90 | ------------------------------ |
| 91 | 91 | ||
| 92 | - * Handle embedded files. PDF Reference 1.7 section 3.10, "File | ||
| 93 | - Specifications", discusses this. Once we can definitely recognize | ||
| 94 | - all embedded files in a document, we can update the encryption | ||
| 95 | - code to handle it properly. In QPDF_encryption.cc, search for | ||
| 96 | - cf_file. Remove exception thrown if cf_file is different from | ||
| 97 | - cf_stream, and write code in the stream decryption section to use | ||
| 98 | - cf_file instead of cf_stream. In general, add interfaces to get | ||
| 99 | - the list of embedded files and to extract them. To handle general | ||
| 100 | - embedded files associated with the whole document, follow root -> | ||
| 101 | - /Names -> /EmbeddedFiles -> /Names to get to the file specification | ||
| 102 | - dictionaries. Then, in each file specification dictionary, follow | ||
| 103 | - /EF -> /F to the actual stream. There may be other places file | ||
| 104 | - specification dictionaries may appear, and there are also /RF keys | ||
| 105 | - with related files, so reread section 3.10 carefully. | 92 | + * Provide APIs for embedded files. See *attachments*.pdf in test |
| 93 | + suite. The private method findAttachmentStreams finds at least | ||
| 94 | + cases for modern versions of Adobe Reader (>= 1.7, maybe earlier). | ||
| 95 | + PDF Reference 1.7 section 3.10, "File Specifications", discusses | ||
| 96 | + this. | ||
| 106 | 97 | ||
| 107 | A sourceforge user asks if qpdf can handle extracting and embedded | 98 | A sourceforge user asks if qpdf can handle extracting and embedded |
| 108 | resources and references these tools, which may be useful as a | 99 | resources and references these tools, which may be useful as a |
include/qpdf/QPDF.hh
| @@ -604,6 +604,7 @@ class QPDF | @@ -604,6 +604,7 @@ class QPDF | ||
| 604 | int& act_objid, int& act_generation); | 604 | int& act_objid, int& act_generation); |
| 605 | PointerHolder<QPDFObject> resolve(int objid, int generation); | 605 | PointerHolder<QPDFObject> resolve(int objid, int generation); |
| 606 | void resolveObjectsInStream(int obj_stream_number); | 606 | void resolveObjectsInStream(int obj_stream_number); |
| 607 | + void findAttachmentStreams(); | ||
| 607 | 608 | ||
| 608 | // Calls finish() on the pipeline when done but does not delete it | 609 | // Calls finish() on the pipeline when done but does not delete it |
| 609 | void pipeStreamData(int objid, int generation, | 610 | void pipeStreamData(int objid, int generation, |
| @@ -1004,6 +1005,7 @@ class QPDF | @@ -1004,6 +1005,7 @@ class QPDF | ||
| 1004 | PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams; | 1005 | PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams; |
| 1005 | // copied_stream_data_provider is owned by copied_streams | 1006 | // copied_stream_data_provider is owned by copied_streams |
| 1006 | CopiedStreamDataProvider* copied_stream_data_provider; | 1007 | CopiedStreamDataProvider* copied_stream_data_provider; |
| 1008 | + std::set<ObjGen> attachment_streams; | ||
| 1007 | 1009 | ||
| 1008 | // Linearization data | 1010 | // Linearization data |
| 1009 | qpdf_offset_t first_xref_item_offset; // actual value from file | 1011 | qpdf_offset_t first_xref_item_offset; // actual value from file |
libqpdf/QPDF.cc
| @@ -314,6 +314,7 @@ QPDF::parse(char const* password) | @@ -314,6 +314,7 @@ QPDF::parse(char const* password) | ||
| 314 | } | 314 | } |
| 315 | 315 | ||
| 316 | initializeEncryption(); | 316 | initializeEncryption(); |
| 317 | + findAttachmentStreams(); | ||
| 317 | } | 318 | } |
| 318 | 319 | ||
| 319 | void | 320 | void |
| @@ -2069,3 +2070,38 @@ QPDF::pipeStreamData(int objid, int generation, | @@ -2069,3 +2070,38 @@ QPDF::pipeStreamData(int objid, int generation, | ||
| 2069 | } | 2070 | } |
| 2070 | pipeline->finish(); | 2071 | pipeline->finish(); |
| 2071 | } | 2072 | } |
| 2073 | + | ||
| 2074 | +void | ||
| 2075 | +QPDF::findAttachmentStreams() | ||
| 2076 | +{ | ||
| 2077 | + QPDFObjectHandle root = getRoot(); | ||
| 2078 | + QPDFObjectHandle names = root.getKey("/Names"); | ||
| 2079 | + if (! names.isDictionary()) | ||
| 2080 | + { | ||
| 2081 | + return; | ||
| 2082 | + } | ||
| 2083 | + QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles"); | ||
| 2084 | + if (! embeddedFiles.isDictionary()) | ||
| 2085 | + { | ||
| 2086 | + return; | ||
| 2087 | + } | ||
| 2088 | + names = embeddedFiles.getKey("/Names"); | ||
| 2089 | + if (! names.isArray()) | ||
| 2090 | + { | ||
| 2091 | + return; | ||
| 2092 | + } | ||
| 2093 | + for (int i = 0; i < names.getArrayNItems(); ++i) | ||
| 2094 | + { | ||
| 2095 | + QPDFObjectHandle item = names.getArrayItem(i); | ||
| 2096 | + if (item.isDictionary() && | ||
| 2097 | + item.getKey("/Type").isName() && | ||
| 2098 | + (item.getKey("/Type").getName() == "/Filespec") && | ||
| 2099 | + item.getKey("/EF").isDictionary() && | ||
| 2100 | + item.getKey("/EF").getKey("/F").isStream()) | ||
| 2101 | + { | ||
| 2102 | + QPDFObjectHandle stream = item.getKey("/EF").getKey("/F"); | ||
| 2103 | + this->attachment_streams.insert( | ||
| 2104 | + ObjGen(stream.getObjectID(), stream.getGeneration())); | ||
| 2105 | + } | ||
| 2106 | + } | ||
| 2107 | +} |
libqpdf/QPDFWriter.cc
| @@ -470,27 +470,13 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) | @@ -470,27 +470,13 @@ QPDFWriter::copyEncryptionParameters(QPDF& qpdf) | ||
| 470 | } | 470 | } |
| 471 | if (V >= 4) | 471 | if (V >= 4) |
| 472 | { | 472 | { |
| 473 | - if (encrypt.hasKey("/CF") && | ||
| 474 | - encrypt.getKey("/CF").isDictionary() && | ||
| 475 | - encrypt.hasKey("/StmF") && | ||
| 476 | - encrypt.getKey("/StmF").isName()) | ||
| 477 | - { | ||
| 478 | - // Determine whether to use AES from StmF. QPDFWriter | ||
| 479 | - // can't write files with different StrF and StmF. | ||
| 480 | - QPDFObjectHandle CF = encrypt.getKey("/CF"); | ||
| 481 | - QPDFObjectHandle StmF = encrypt.getKey("/StmF"); | ||
| 482 | - if (CF.hasKey(StmF.getName()) && | ||
| 483 | - CF.getKey(StmF.getName()).isDictionary()) | ||
| 484 | - { | ||
| 485 | - QPDFObjectHandle StmF_data = CF.getKey(StmF.getName()); | ||
| 486 | - if (StmF_data.hasKey("/CFM") && | ||
| 487 | - StmF_data.getKey("/CFM").isName() && | ||
| 488 | - StmF_data.getKey("/CFM").getName() == "/AESV2") | ||
| 489 | - { | ||
| 490 | - this->encrypt_use_aes = true; | ||
| 491 | - } | ||
| 492 | - } | ||
| 493 | - } | 473 | + // When copying encryption parameters, use AES even if the |
| 474 | + // original file did not. Acrobat doesn't create files | ||
| 475 | + // with V >= 4 that don't use AES, and the logic of | ||
| 476 | + // figuring out whether AES is used or not is complicated | ||
| 477 | + // with /StmF, /StrF, and /EFF all potentially having | ||
| 478 | + // different values. | ||
| 479 | + this->encrypt_use_aes = true; | ||
| 494 | } | 480 | } |
| 495 | QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", | 481 | QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", |
| 496 | this->encrypt_metadata ? 0 : 1); | 482 | this->encrypt_metadata ? 0 : 1); |
libqpdf/QPDF_Stream.cc
| @@ -91,6 +91,80 @@ QPDF_Stream::getRawStreamData() | @@ -91,6 +91,80 @@ QPDF_Stream::getRawStreamData() | ||
| 91 | } | 91 | } |
| 92 | 92 | ||
| 93 | bool | 93 | bool |
| 94 | +QPDF_Stream::understandDecodeParams( | ||
| 95 | + std::string const& filter, QPDFObjectHandle decode_obj, | ||
| 96 | + int& predictor, int& columns, bool& early_code_change) | ||
| 97 | +{ | ||
| 98 | + bool filterable = true; | ||
| 99 | + std::set<std::string> keys = decode_obj.getKeys(); | ||
| 100 | + for (std::set<std::string>::iterator iter = keys.begin(); | ||
| 101 | + iter != keys.end(); ++iter) | ||
| 102 | + { | ||
| 103 | + std::string const& key = *iter; | ||
| 104 | + if ((filter == "/FlateDecode") && (key == "/Predictor")) | ||
| 105 | + { | ||
| 106 | + QPDFObjectHandle predictor_obj = decode_obj.getKey(key); | ||
| 107 | + if (predictor_obj.isInteger()) | ||
| 108 | + { | ||
| 109 | + predictor = predictor_obj.getIntValue(); | ||
| 110 | + if (! ((predictor == 1) || (predictor == 12))) | ||
| 111 | + { | ||
| 112 | + filterable = false; | ||
| 113 | + } | ||
| 114 | + } | ||
| 115 | + else | ||
| 116 | + { | ||
| 117 | + filterable = false; | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + else if ((filter == "/LZWDecode") && (key == "/EarlyChange")) | ||
| 121 | + { | ||
| 122 | + QPDFObjectHandle earlychange_obj = decode_obj.getKey(key); | ||
| 123 | + if (earlychange_obj.isInteger()) | ||
| 124 | + { | ||
| 125 | + int earlychange = earlychange_obj.getIntValue(); | ||
| 126 | + early_code_change = (earlychange == 1); | ||
| 127 | + if (! ((earlychange == 0) || (earlychange == 1))) | ||
| 128 | + { | ||
| 129 | + filterable = false; | ||
| 130 | + } | ||
| 131 | + } | ||
| 132 | + else | ||
| 133 | + { | ||
| 134 | + filterable = false; | ||
| 135 | + } | ||
| 136 | + } | ||
| 137 | + else if (key == "/Columns") | ||
| 138 | + { | ||
| 139 | + QPDFObjectHandle columns_obj = decode_obj.getKey(key); | ||
| 140 | + if (columns_obj.isInteger()) | ||
| 141 | + { | ||
| 142 | + columns = columns_obj.getIntValue(); | ||
| 143 | + } | ||
| 144 | + else | ||
| 145 | + { | ||
| 146 | + filterable = false; | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + else if ((filter == "/Crypt") && | ||
| 150 | + (((key == "/Type") || (key == "/Name")) && | ||
| 151 | + (decode_obj.getKey("/Type").isNull() || | ||
| 152 | + (decode_obj.getKey("/Type").isName() && | ||
| 153 | + (decode_obj.getKey("/Type").getName() == | ||
| 154 | + "/CryptFilterDecodeParms"))))) | ||
| 155 | + { | ||
| 156 | + // we handle this in decryptStream | ||
| 157 | + } | ||
| 158 | + else | ||
| 159 | + { | ||
| 160 | + filterable = false; | ||
| 161 | + } | ||
| 162 | + } | ||
| 163 | + | ||
| 164 | + return filterable; | ||
| 165 | +} | ||
| 166 | + | ||
| 167 | +bool | ||
| 94 | QPDF_Stream::filterable(std::vector<std::string>& filters, | 168 | QPDF_Stream::filterable(std::vector<std::string>& filters, |
| 95 | int& predictor, int& columns, | 169 | int& predictor, int& columns, |
| 96 | bool& early_code_change) | 170 | bool& early_code_change) |
| @@ -110,106 +184,6 @@ QPDF_Stream::filterable(std::vector<std::string>& filters, | @@ -110,106 +184,6 @@ QPDF_Stream::filterable(std::vector<std::string>& filters, | ||
| 110 | filter_abbreviations["/DCT"] = "/DCTDecode"; | 184 | filter_abbreviations["/DCT"] = "/DCTDecode"; |
| 111 | } | 185 | } |
| 112 | 186 | ||
| 113 | - // Initialize values to their defaults as per the PDF spec | ||
| 114 | - predictor = 1; | ||
| 115 | - columns = 0; | ||
| 116 | - early_code_change = true; | ||
| 117 | - | ||
| 118 | - bool filterable = true; | ||
| 119 | - | ||
| 120 | - // See if we can support any decode parameters that are specified. | ||
| 121 | - | ||
| 122 | - QPDFObjectHandle decode_obj = | ||
| 123 | - this->stream_dict.getKey("/DecodeParms"); | ||
| 124 | - if (decode_obj.isNull()) | ||
| 125 | - { | ||
| 126 | - // no problem | ||
| 127 | - } | ||
| 128 | - else if (decode_obj.isDictionary()) | ||
| 129 | - { | ||
| 130 | - std::set<std::string> keys = decode_obj.getKeys(); | ||
| 131 | - for (std::set<std::string>::iterator iter = keys.begin(); | ||
| 132 | - iter != keys.end(); ++iter) | ||
| 133 | - { | ||
| 134 | - std::string const& key = *iter; | ||
| 135 | - if (key == "/Predictor") | ||
| 136 | - { | ||
| 137 | - QPDFObjectHandle predictor_obj = decode_obj.getKey(key); | ||
| 138 | - if (predictor_obj.isInteger()) | ||
| 139 | - { | ||
| 140 | - predictor = predictor_obj.getIntValue(); | ||
| 141 | - if (! ((predictor == 1) || (predictor == 12))) | ||
| 142 | - { | ||
| 143 | - filterable = false; | ||
| 144 | - } | ||
| 145 | - } | ||
| 146 | - else | ||
| 147 | - { | ||
| 148 | - filterable = false; | ||
| 149 | - } | ||
| 150 | - } | ||
| 151 | - else if (key == "/EarlyChange") | ||
| 152 | - { | ||
| 153 | - QPDFObjectHandle earlychange_obj = decode_obj.getKey(key); | ||
| 154 | - if (earlychange_obj.isInteger()) | ||
| 155 | - { | ||
| 156 | - int earlychange = earlychange_obj.getIntValue(); | ||
| 157 | - early_code_change = (earlychange == 1); | ||
| 158 | - if (! ((earlychange == 0) || (earlychange == 1))) | ||
| 159 | - { | ||
| 160 | - filterable = false; | ||
| 161 | - } | ||
| 162 | - } | ||
| 163 | - else | ||
| 164 | - { | ||
| 165 | - filterable = false; | ||
| 166 | - } | ||
| 167 | - } | ||
| 168 | - else if (key == "/Columns") | ||
| 169 | - { | ||
| 170 | - QPDFObjectHandle columns_obj = decode_obj.getKey(key); | ||
| 171 | - if (columns_obj.isInteger()) | ||
| 172 | - { | ||
| 173 | - columns = columns_obj.getIntValue(); | ||
| 174 | - } | ||
| 175 | - else | ||
| 176 | - { | ||
| 177 | - filterable = false; | ||
| 178 | - } | ||
| 179 | - } | ||
| 180 | - else if (((key == "/Type") || (key == "/Name")) && | ||
| 181 | - decode_obj.getKey("/Type").isName() && | ||
| 182 | - (decode_obj.getKey("/Type").getName() == | ||
| 183 | - "/CryptFilterDecodeParms")) | ||
| 184 | - { | ||
| 185 | - // we handle this in decryptStream | ||
| 186 | - } | ||
| 187 | - else | ||
| 188 | - { | ||
| 189 | - filterable = false; | ||
| 190 | - } | ||
| 191 | - } | ||
| 192 | - } | ||
| 193 | - else | ||
| 194 | - { | ||
| 195 | - // Ignore for now -- some filter types, like CCITTFaxDecode, | ||
| 196 | - // use types other than dictionary for this. | ||
| 197 | - QTC::TC("qpdf", "QPDF_Stream ignore non-dictionary DecodeParms"); | ||
| 198 | - | ||
| 199 | - filterable = false; | ||
| 200 | - } | ||
| 201 | - | ||
| 202 | - if ((predictor > 1) && (columns == 0)) | ||
| 203 | - { | ||
| 204 | - // invalid | ||
| 205 | - filterable = false; | ||
| 206 | - } | ||
| 207 | - | ||
| 208 | - if (! filterable) | ||
| 209 | - { | ||
| 210 | - return false; | ||
| 211 | - } | ||
| 212 | - | ||
| 213 | // Check filters | 187 | // Check filters |
| 214 | 188 | ||
| 215 | QPDFObjectHandle filter_obj = this->stream_dict.getKey("/Filter"); | 189 | QPDFObjectHandle filter_obj = this->stream_dict.getKey("/Filter"); |
| @@ -254,8 +228,7 @@ QPDF_Stream::filterable(std::vector<std::string>& filters, | @@ -254,8 +228,7 @@ QPDF_Stream::filterable(std::vector<std::string>& filters, | ||
| 254 | "stream filter type is not name or array"); | 228 | "stream filter type is not name or array"); |
| 255 | } | 229 | } |
| 256 | 230 | ||
| 257 | - // `filters' now contains a list of filters to be applied in | ||
| 258 | - // order. See which ones we can support. | 231 | + bool filterable = true; |
| 259 | 232 | ||
| 260 | for (std::vector<std::string>::iterator iter = filters.begin(); | 233 | for (std::vector<std::string>::iterator iter = filters.begin(); |
| 261 | iter != filters.end(); ++iter) | 234 | iter != filters.end(); ++iter) |
| @@ -278,6 +251,79 @@ QPDF_Stream::filterable(std::vector<std::string>& filters, | @@ -278,6 +251,79 @@ QPDF_Stream::filterable(std::vector<std::string>& filters, | ||
| 278 | } | 251 | } |
| 279 | } | 252 | } |
| 280 | 253 | ||
| 254 | + if (! filterable) | ||
| 255 | + { | ||
| 256 | + return false; | ||
| 257 | + } | ||
| 258 | + | ||
| 259 | + // `filters' now contains a list of filters to be applied in | ||
| 260 | + // order. See which ones we can support. | ||
| 261 | + | ||
| 262 | + // Initialize values to their defaults as per the PDF spec | ||
| 263 | + predictor = 1; | ||
| 264 | + columns = 0; | ||
| 265 | + early_code_change = true; | ||
| 266 | + | ||
| 267 | + // See if we can support any decode parameters that are specified. | ||
| 268 | + | ||
| 269 | + QPDFObjectHandle decode_obj = this->stream_dict.getKey("/DecodeParms"); | ||
| 270 | + std::vector<QPDFObjectHandle> decode_parms; | ||
| 271 | + if (decode_obj.isArray()) | ||
| 272 | + { | ||
| 273 | + for (int i = 0; i < decode_obj.getArrayNItems(); ++i) | ||
| 274 | + { | ||
| 275 | + decode_parms.push_back(decode_obj.getArrayItem(i)); | ||
| 276 | + } | ||
| 277 | + } | ||
| 278 | + else | ||
| 279 | + { | ||
| 280 | + for (unsigned int i = 0; i < filters.size(); ++i) | ||
| 281 | + { | ||
| 282 | + decode_parms.push_back(decode_obj); | ||
| 283 | + } | ||
| 284 | + } | ||
| 285 | + | ||
| 286 | + if (decode_parms.size() != filters.size()) | ||
| 287 | + { | ||
| 288 | + throw QPDFExc(qpdf_e_damaged_pdf, qpdf->getFilename(), | ||
| 289 | + "", this->offset, | ||
| 290 | + "stream /DecodeParms length is" | ||
| 291 | + " inconsistent with filters"); | ||
| 292 | + } | ||
| 293 | + | ||
| 294 | + for (unsigned int i = 0; i < filters.size(); ++i) | ||
| 295 | + { | ||
| 296 | + QPDFObjectHandle decode_item = decode_parms[i]; | ||
| 297 | + if (decode_item.isNull()) | ||
| 298 | + { | ||
| 299 | + // okay | ||
| 300 | + } | ||
| 301 | + else if (decode_item.isDictionary()) | ||
| 302 | + { | ||
| 303 | + if (! understandDecodeParams( | ||
| 304 | + filters[i], decode_item, | ||
| 305 | + predictor, columns, early_code_change)) | ||
| 306 | + { | ||
| 307 | + filterable = false; | ||
| 308 | + } | ||
| 309 | + } | ||
| 310 | + else | ||
| 311 | + { | ||
| 312 | + filterable = false; | ||
| 313 | + } | ||
| 314 | + } | ||
| 315 | + | ||
| 316 | + if ((predictor > 1) && (columns == 0)) | ||
| 317 | + { | ||
| 318 | + // invalid | ||
| 319 | + filterable = false; | ||
| 320 | + } | ||
| 321 | + | ||
| 322 | + if (! filterable) | ||
| 323 | + { | ||
| 324 | + return false; | ||
| 325 | + } | ||
| 326 | + | ||
| 281 | return filterable; | 327 | return filterable; |
| 282 | } | 328 | } |
| 283 | 329 |
libqpdf/QPDF_encryption.cc
| @@ -573,28 +573,6 @@ QPDF::initializeEncryption() | @@ -573,28 +573,6 @@ QPDF::initializeEncryption() | ||
| 573 | { | 573 | { |
| 574 | this->cf_file = this->cf_stream; | 574 | this->cf_file = this->cf_stream; |
| 575 | } | 575 | } |
| 576 | - if (this->cf_file != this->cf_stream) | ||
| 577 | - { | ||
| 578 | - // The issue for qpdf is that it can't tell the difference | ||
| 579 | - // between an embedded file stream and a regular stream. | ||
| 580 | - // Search for a comment containing cf_file. To fix this, | ||
| 581 | - // we need files with encrypted embedded files and | ||
| 582 | - // non-encrypted native streams and vice versa. Also if | ||
| 583 | - // it is possible for them to be encrypted in different | ||
| 584 | - // ways, we should have some of those too. In cases where | ||
| 585 | - // we can detect whether a stream is encrypted or not, we | ||
| 586 | - // might want to try to detecet that automatically in | ||
| 587 | - // defense of possible logic errors surrounding detection | ||
| 588 | - // of embedded file streams, unless that's really clear | ||
| 589 | - // from the specification. | ||
| 590 | - throw QPDFExc(qpdf_e_unsupported, this->file->getName(), | ||
| 591 | - "encryption dictionary", this->file->getLastOffset(), | ||
| 592 | - "This document has embedded files that are" | ||
| 593 | - " encrypted differently from the rest of the file." | ||
| 594 | - " qpdf does not presently support this due to" | ||
| 595 | - " lack of test data; if possible, please submit" | ||
| 596 | - " a bug report that includes this file."); | ||
| 597 | - } | ||
| 598 | } | 576 | } |
| 599 | EncryptionData data(V, R, Length / 8, P, O, U, "", "", "", | 577 | EncryptionData data(V, R, Length / 8, P, O, U, "", "", "", |
| 600 | id1, this->encrypt_metadata); | 578 | id1, this->encrypt_metadata); |
| @@ -737,18 +715,48 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | @@ -737,18 +715,48 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | ||
| 737 | encryption_method_e method = e_unknown; | 715 | encryption_method_e method = e_unknown; |
| 738 | std::string method_source = "/StmF from /Encrypt dictionary"; | 716 | std::string method_source = "/StmF from /Encrypt dictionary"; |
| 739 | 717 | ||
| 740 | - if (stream_dict.getKey("/Filter").isOrHasName("/Crypt") && | ||
| 741 | - stream_dict.getKey("/DecodeParms").isDictionary()) | ||
| 742 | - { | ||
| 743 | - QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms"); | ||
| 744 | - if (decode_parms.getKey("/Type").isName() && | ||
| 745 | - (decode_parms.getKey("/Type").getName() == | ||
| 746 | - "/CryptFilterDecodeParms")) | ||
| 747 | - { | ||
| 748 | - QTC::TC("qpdf", "QPDF_encryption stream crypt filter"); | ||
| 749 | - method = interpretCF(decode_parms.getKey("/Name")); | ||
| 750 | - method_source = "stream's Crypt decode parameters"; | ||
| 751 | - } | 718 | + if (stream_dict.getKey("/Filter").isOrHasName("/Crypt")) |
| 719 | + { | ||
| 720 | + if (stream_dict.getKey("/DecodeParms").isDictionary()) | ||
| 721 | + { | ||
| 722 | + QPDFObjectHandle decode_parms = | ||
| 723 | + stream_dict.getKey("/DecodeParms"); | ||
| 724 | + if (decode_parms.getKey("/Type").isName() && | ||
| 725 | + (decode_parms.getKey("/Type").getName() == | ||
| 726 | + "/CryptFilterDecodeParms")) | ||
| 727 | + { | ||
| 728 | + QTC::TC("qpdf", "QPDF_encryption stream crypt filter"); | ||
| 729 | + method = interpretCF(decode_parms.getKey("/Name")); | ||
| 730 | + method_source = "stream's Crypt decode parameters"; | ||
| 731 | + } | ||
| 732 | + } | ||
| 733 | + else if (stream_dict.getKey("/DecodeParms").isArray() && | ||
| 734 | + stream_dict.getKey("/Filter").isArray()) | ||
| 735 | + { | ||
| 736 | + QPDFObjectHandle filter = stream_dict.getKey("/Filter"); | ||
| 737 | + QPDFObjectHandle decode = stream_dict.getKey("/DecodeParms"); | ||
| 738 | + if (filter.getArrayNItems() == decode.getArrayNItems()) | ||
| 739 | + { | ||
| 740 | + for (int i = 0; i < filter.getArrayNItems(); ++i) | ||
| 741 | + { | ||
| 742 | + if (filter.getArrayItem(i).isName() && | ||
| 743 | + (filter.getArrayItem(i).getName() == "/Crypt")) | ||
| 744 | + { | ||
| 745 | + QPDFObjectHandle crypt_params = | ||
| 746 | + decode.getArrayItem(i); | ||
| 747 | + if (crypt_params.isDictionary() && | ||
| 748 | + crypt_params.getKey("/Name").isName()) | ||
| 749 | + { | ||
| 750 | +// XXX QTC::TC("qpdf", "QPDF_encrypt crypt array"); | ||
| 751 | + method = interpretCF( | ||
| 752 | + crypt_params.getKey("/Name")); | ||
| 753 | + method_source = "stream's Crypt " | ||
| 754 | + "decode parameters (array)"; | ||
| 755 | + } | ||
| 756 | + } | ||
| 757 | + } | ||
| 758 | + } | ||
| 759 | + } | ||
| 752 | } | 760 | } |
| 753 | 761 | ||
| 754 | if (method == e_unknown) | 762 | if (method == e_unknown) |
| @@ -760,12 +768,15 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | @@ -760,12 +768,15 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, | ||
| 760 | } | 768 | } |
| 761 | else | 769 | else |
| 762 | { | 770 | { |
| 763 | - // NOTE: We should should use cf_file if this is an | ||
| 764 | - // embedded file, but we can't yet detect embedded | ||
| 765 | - // file streams as such. When fixing, search for all | ||
| 766 | - // occurrences of cf_file to find a reference to this | ||
| 767 | - // comment. | ||
| 768 | - method = this->cf_stream; | 771 | + if (this->attachment_streams.count( |
| 772 | + ObjGen(objid, generation)) > 0) | ||
| 773 | + { | ||
| 774 | + method = this->cf_file; | ||
| 775 | + } | ||
| 776 | + else | ||
| 777 | + { | ||
| 778 | + method = this->cf_stream; | ||
| 779 | + } | ||
| 769 | } | 780 | } |
| 770 | } | 781 | } |
| 771 | use_aes = false; | 782 | use_aes = false; |
libqpdf/qpdf/QPDF_Stream.hh
| @@ -45,6 +45,9 @@ class QPDF_Stream: public QPDFObject | @@ -45,6 +45,9 @@ class QPDF_Stream: public QPDFObject | ||
| 45 | void replaceFilterData(QPDFObjectHandle const& filter, | 45 | void replaceFilterData(QPDFObjectHandle const& filter, |
| 46 | QPDFObjectHandle const& decode_parms, | 46 | QPDFObjectHandle const& decode_parms, |
| 47 | size_t length); | 47 | size_t length); |
| 48 | + bool understandDecodeParams( | ||
| 49 | + std::string const& filter, QPDFObjectHandle decode_params, | ||
| 50 | + int& predictor, int& columns, bool& early_code_change); | ||
| 48 | bool filterable(std::vector<std::string>& filters, | 51 | bool filterable(std::vector<std::string>& filters, |
| 49 | int& predictor, int& columns, bool& early_code_change); | 52 | int& predictor, int& columns, bool& early_code_change); |
| 50 | 53 |
qpdf/qpdf.testcov
| @@ -116,7 +116,6 @@ qpdf unable to filter 0 | @@ -116,7 +116,6 @@ qpdf unable to filter 0 | ||
| 116 | QPDF_String non-trivial UTF-16 0 | 116 | QPDF_String non-trivial UTF-16 0 |
| 117 | QPDF xref overwrite object 0 | 117 | QPDF xref overwrite object 0 |
| 118 | QPDF decoding error warning 0 | 118 | QPDF decoding error warning 0 |
| 119 | -QPDF_Stream ignore non-dictionary DecodeParms 0 | ||
| 120 | qpdf-c called qpdf_init 0 | 119 | qpdf-c called qpdf_init 0 |
| 121 | qpdf-c called qpdf_cleanup 0 | 120 | qpdf-c called qpdf_cleanup 0 |
| 122 | qpdf-c called qpdf_more_warnings 0 | 121 | qpdf-c called qpdf_more_warnings 0 |
qpdf/qtest/qpdf/obj0-check.out
| 1 | -checking obj0.pdf | ||
| 2 | WARNING: obj0.pdf: file is damaged | 1 | WARNING: obj0.pdf: file is damaged |
| 3 | WARNING: obj0.pdf (object 1 0, file position 77): expected n n obj | 2 | WARNING: obj0.pdf (object 1 0, file position 77): expected n n obj |
| 4 | WARNING: obj0.pdf: Attempting to reconstruct cross-reference table | 3 | WARNING: obj0.pdf: Attempting to reconstruct cross-reference table |
| 4 | +checking obj0.pdf | ||
| 5 | PDF Version: 1.3 | 5 | PDF Version: 1.3 |
| 6 | File is not encrypted | 6 | File is not encrypted |
| 7 | File is not linearized | 7 | File is not linearized |