Commit 734ac1e1d2b3ce10a2da1a7e736a30bdf0bc5cf8
1 parent
70ae58c0
deal with stream-specific crypt filters
git-svn-id: svn+q:///qpdf/trunk@827 71b93d88-0707-0410-a8cf-f5a4172ac649
Showing
9 changed files
with
483 additions
and
24 deletions
TODO
| 1 | 1 | 2.1 |
| 2 | 2 | === |
| 3 | 3 | |
| 4 | - * Really need to handle /Crypt filter for Metadata. Search for crypt | |
| 5 | - below. | |
| 6 | - | |
| 7 | 4 | * Update documentation to reflect new command line flags and any |
| 8 | 5 | other relevant changes. Should read through ChangeLog and the |
| 9 | 6 | manual before releasing 2.1. |
| ... | ... | @@ -83,10 +80,13 @@ General |
| 83 | 80 | filters. There is an example in the spec of using a crypt filter |
| 84 | 81 | on a metadata stream. |
| 85 | 82 | |
| 86 | - When we write encrypted files, we must remember to omit any | |
| 87 | - encryption filter settings from original streams. | |
| 88 | - | |
| 89 | - We need a way to test this. | |
| 83 | + For now, we notice /Crypt filters and decode parameters consistent | |
| 84 | + with the example in the PDF specification, and the right thing | |
| 85 | + happens for metadata filters that happen to be uncompressed or | |
| 86 | + otherwise compressed in a way we can filter. This should handle | |
| 87 | + all normal cases, but it's more or less just a guess since I don't | |
| 88 | + have any test files that actually use stream-specific crypt filters | |
| 89 | + in them. | |
| 90 | 90 | |
| 91 | 91 | * The second xref stream for linearized files has to be padded only |
| 92 | 92 | because we need file_size as computed in pass 1 to be accurate. If | ... | ... |
include/qpdf/QPDFObjectHandle.hh
| ... | ... | @@ -77,14 +77,14 @@ class DLL_EXPORT QPDFObjectHandle |
| 77 | 77 | bool isNumber(); |
| 78 | 78 | double getNumericValue(); |
| 79 | 79 | |
| 80 | - // Methods for name objects | |
| 80 | + // Methods for name objects; see also name and array objects | |
| 81 | 81 | std::string getName(); |
| 82 | 82 | |
| 83 | 83 | // Methods for string objects |
| 84 | 84 | std::string getStringValue(); |
| 85 | 85 | std::string getUTF8Value(); |
| 86 | 86 | |
| 87 | - // Methods for array objects | |
| 87 | + // Methods for array objects; see also name and array objects | |
| 88 | 88 | int getArrayNItems(); |
| 89 | 89 | QPDFObjectHandle getArrayItem(int n); |
| 90 | 90 | |
| ... | ... | @@ -93,6 +93,9 @@ class DLL_EXPORT QPDFObjectHandle |
| 93 | 93 | QPDFObjectHandle getKey(std::string const&); |
| 94 | 94 | std::set<std::string> getKeys(); |
| 95 | 95 | |
| 96 | + // Methods for name and array objects | |
| 97 | + bool isOrHasName(std::string const&); | |
| 98 | + | |
| 96 | 99 | // Mutator methods. Use with caution. |
| 97 | 100 | |
| 98 | 101 | // Recursively copy this object, making it direct. Throws an | ... | ... |
libqpdf/QPDFObjectHandle.cc
| ... | ... | @@ -258,6 +258,29 @@ QPDFObjectHandle::getKeys() |
| 258 | 258 | return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->getKeys(); |
| 259 | 259 | } |
| 260 | 260 | |
| 261 | +// Array and Name accessors | |
| 262 | +bool | |
| 263 | +QPDFObjectHandle::isOrHasName(std::string const& value) | |
| 264 | +{ | |
| 265 | + if (isName() && (getName() == value)) | |
| 266 | + { | |
| 267 | + return true; | |
| 268 | + } | |
| 269 | + else if (isArray()) | |
| 270 | + { | |
| 271 | + int n = getArrayNItems(); | |
| 272 | + for (int i = 0; i < n; ++i) | |
| 273 | + { | |
| 274 | + QPDFObjectHandle item = getArrayItem(0); | |
| 275 | + if (item.isName() && (item.getName() == value)) | |
| 276 | + { | |
| 277 | + return true; | |
| 278 | + } | |
| 279 | + } | |
| 280 | + } | |
| 281 | + return false; | |
| 282 | +} | |
| 283 | + | |
| 261 | 284 | // Dictionary mutators |
| 262 | 285 | |
| 263 | 286 | void | ... | ... |
libqpdf/QPDF_Stream.cc
| ... | ... | @@ -136,6 +136,13 @@ QPDF_Stream::filterable(std::vector<std::string>& filters, |
| 136 | 136 | filterable = false; |
| 137 | 137 | } |
| 138 | 138 | } |
| 139 | + else if (((key == "/Type") || (key == "/Name")) && | |
| 140 | + decode_obj.getKey("/Type").isName() && | |
| 141 | + (decode_obj.getKey("/Type").getName() == | |
| 142 | + "/CryptFilterDecodeParms")) | |
| 143 | + { | |
| 144 | + // we handle this in decryptStream | |
| 145 | + } | |
| 139 | 146 | else |
| 140 | 147 | { |
| 141 | 148 | filterable = false; |
| ... | ... | @@ -212,7 +219,8 @@ QPDF_Stream::filterable(std::vector<std::string>& filters, |
| 212 | 219 | iter != filters.end(); ++iter) |
| 213 | 220 | { |
| 214 | 221 | std::string const& filter = *iter; |
| 215 | - if (! ((filter == "/FlateDecode") || | |
| 222 | + if (! ((filter == "/Crypt") || | |
| 223 | + (filter == "/FlateDecode") || | |
| 216 | 224 | (filter == "/LZWDecode") || |
| 217 | 225 | (filter == "/ASCII85Decode") || |
| 218 | 226 | (filter == "/ASCIIHexDecode"))) |
| ... | ... | @@ -266,7 +274,11 @@ QPDF_Stream::pipeStreamData(Pipeline* pipeline, bool filter, |
| 266 | 274 | iter != filters.rend(); ++iter) |
| 267 | 275 | { |
| 268 | 276 | std::string const& filter = *iter; |
| 269 | - if (filter == "/FlateDecode") | |
| 277 | + if (filter == "/Crypt") | |
| 278 | + { | |
| 279 | + // Ignore -- handled by pipeStreamData | |
| 280 | + } | |
| 281 | + else if (filter == "/FlateDecode") | |
| 270 | 282 | { |
| 271 | 283 | if (predictor == 12) |
| 272 | 284 | { | ... | ... |
libqpdf/QPDF_encryption.cc
| ... | ... | @@ -600,18 +600,19 @@ QPDF::decryptStream(Pipeline*& pipeline, int objid, int generation, |
| 600 | 600 | encryption_method_e method = e_unknown; |
| 601 | 601 | std::string method_source = "/StmF from /Encrypt dictionary"; |
| 602 | 602 | |
| 603 | - // NOTE: the section in the PDF specification on crypt filters | |
| 604 | - // seems to suggest that there might be a /Crypt key in | |
| 605 | - // /DecodeParms whose value is a crypt filter (.e.g., << /Name | |
| 606 | - // /StdCF >>), but implementation notes suggest this can only | |
| 607 | - // happen for metadata streams, and emperical observation | |
| 608 | - // suggests that they are otherwise ignored. Not having been | |
| 609 | - // able to find a sample file that uses crypt filters in any | |
| 610 | - // way other than /StrF and /StmF, I'm not really sure what to | |
| 611 | - // do about this. If we were to override the encryption on a | |
| 612 | - // per-stream basis using crypt filters, set method_source to | |
| 613 | - // something useful in the error message for unknown | |
| 614 | - // encryption methods (search for method_source). | |
| 603 | + if (stream_dict.getKey("/Filter").isOrHasName("/Crypt") && | |
| 604 | + stream_dict.getKey("/DecodeParms").isDictionary()) | |
| 605 | + { | |
| 606 | + QPDFObjectHandle decode_parms = stream_dict.getKey("/DecodeParms"); | |
| 607 | + if (decode_parms.getKey("/Type").isName() && | |
| 608 | + (decode_parms.getKey("/Type").getName() == | |
| 609 | + "/CryptFilterDecodeParms")) | |
| 610 | + { | |
| 611 | + QTC::TC("qpdf", "QPDF_encryption stream crypt filter"); | |
| 612 | + method = interpretCF(decode_parms.getKey("/Name")); | |
| 613 | + method_source = "stream's Crypt decode parameters"; | |
| 614 | + } | |
| 615 | + } | |
| 615 | 616 | |
| 616 | 617 | if (method == e_unknown) |
| 617 | 618 | { | ... | ... |
qpdf/qpdf.testcov
qpdf/qtest/qpdf.test
| ... | ... | @@ -1079,7 +1079,7 @@ $td->runtest("make sure there is no xref stream", |
| 1079 | 1079 | $td->NORMALIZE_NEWLINES); |
| 1080 | 1080 | |
| 1081 | 1081 | # Look at some actual V4 files |
| 1082 | -$n_tests += 8; | |
| 1082 | +$n_tests += 10; | |
| 1083 | 1083 | foreach my $d (['--force-V4', 'V4'], |
| 1084 | 1084 | ['--cleartext-metadata', 'V4-clearmeta'], |
| 1085 | 1085 | ['--use-aes=y', 'V4-aes'], |
| ... | ... | @@ -1094,6 +1094,14 @@ foreach my $d (['--force-V4', 'V4'], |
| 1094 | 1094 | {$td->FILE => "a.pdf"}, |
| 1095 | 1095 | {$td->FILE => "$out.pdf"}); |
| 1096 | 1096 | } |
| 1097 | +# Crypt Filter | |
| 1098 | +$td->runtest("decrypt with crypt filter", | |
| 1099 | + {$td->COMMAND => "qpdf --decrypt --static-id" . | |
| 1100 | + " metadata-crypt-filter.pdf a.pdf"}, | |
| 1101 | + {$td->STRING => "", $td->EXIT_STATUS => 0}); | |
| 1102 | +$td->runtest("check output", | |
| 1103 | + {$td->FILE => 'a.pdf'}, | |
| 1104 | + {$td->FILE => 'decrypted-crypt-filter.pdf'}); | |
| 1097 | 1105 | |
| 1098 | 1106 | show_ntests(); |
| 1099 | 1107 | # ---------- | ... | ... |
qpdf/qtest/qpdf/decrypted-crypt-filter.pdf
0 → 100644
No preview for this file type
qpdf/qtest/qpdf/metadata-crypt-filter.pdf
0 → 100644
No preview for this file type