Commit 9a23c3dcb675a62e6a8b144304bdc91cae365c4c
1 parent
4237a29c
Remove /Crypt from stream filters unconditionally
When writing a new stream, always remove /Crypt even if we are not otherwise able to filter the stream.
Showing
7 changed files
with
102 additions
and
4 deletions
libqpdf/QPDFWriter.cc
| ... | ... | @@ -1297,12 +1297,53 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, |
| 1297 | 1297 | // Suppress /Length since we will write it manually |
| 1298 | 1298 | object.removeKey("/Length"); |
| 1299 | 1299 | |
| 1300 | - // XXX BUG: /Crypt filters should always be removed. | |
| 1301 | 1300 | if (flags & f_filtered) |
| 1302 | 1301 | { |
| 1302 | + // We will supply our own filter and decode | |
| 1303 | + // parameters. | |
| 1303 | 1304 | object.removeKey("/Filter"); |
| 1304 | 1305 | object.removeKey("/DecodeParms"); |
| 1305 | 1306 | } |
| 1307 | + else | |
| 1308 | + { | |
| 1309 | + // Make sure, no matter what else we have, that we | |
| 1310 | + // don't have /Crypt in the output filters. | |
| 1311 | + QPDFObjectHandle filter = object.getKey("/Filter"); | |
| 1312 | + QPDFObjectHandle decode_parms = object.getKey("/DecodeParms"); | |
| 1313 | + if (filter.isOrHasName("/Crypt")) | |
| 1314 | + { | |
| 1315 | + if (filter.isName()) | |
| 1316 | + { | |
| 1317 | + object.removeKey("/Filter"); | |
| 1318 | + object.removeKey("/DecodeParms"); | |
| 1319 | + } | |
| 1320 | + else | |
| 1321 | + { | |
| 1322 | + int idx = -1; | |
| 1323 | + for (int i = 0; i < filter.getArrayNItems(); ++i) | |
| 1324 | + { | |
| 1325 | + QPDFObjectHandle item = filter.getArrayItem(i); | |
| 1326 | + if (item.isName() && item.getName() == "/Crypt") | |
| 1327 | + { | |
| 1328 | + idx = i; | |
| 1329 | + break; | |
| 1330 | + } | |
| 1331 | + } | |
| 1332 | + if (idx >= 0) | |
| 1333 | + { | |
| 1334 | + // If filter is an array, then the code in | |
| 1335 | + // QPDF_Stream has already verified that | |
| 1336 | + // DecodeParms and Filters are arrays of | |
| 1337 | + // the same length, but if they weren't | |
| 1338 | + // for some reason, eraseItem does type | |
| 1339 | + // and bounds checking. | |
| 1340 | + QTC::TC("qpdf", "QPDFWriter remove Crypt"); | |
| 1341 | + filter.eraseItem(idx); | |
| 1342 | + decode_parms.eraseItem(idx); | |
| 1343 | + } | |
| 1344 | + } | |
| 1345 | + } | |
| 1346 | + } | |
| 1306 | 1347 | } |
| 1307 | 1348 | |
| 1308 | 1349 | writeString("<<"); | ... | ... |
qpdf/qpdf.testcov
qpdf/qtest/qpdf.test
| ... | ... | @@ -1701,12 +1701,12 @@ my @attachments = ( |
| 1701 | 1701 | 'enc-XI-attachments-base.pdf', |
| 1702 | 1702 | 'enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf', |
| 1703 | 1703 | 'enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf'); |
| 1704 | -$n_tests += 4 * @attachments; | |
| 1704 | +$n_tests += 4 * @attachments + 3; | |
| 1705 | 1705 | foreach my $f (@attachments) |
| 1706 | 1706 | { |
| 1707 | 1707 | my $pass = ''; |
| 1708 | 1708 | my $tpass = ''; |
| 1709 | - if ($f =~ m/U=([^,]+)/) | |
| 1709 | + if ($f =~ m/U=([^,\.]+)/) | |
| 1710 | 1710 | { |
| 1711 | 1711 | $pass = "--password=$1"; |
| 1712 | 1712 | $tpass = $1; |
| ... | ... | @@ -1726,6 +1726,23 @@ foreach my $f (@attachments) |
| 1726 | 1726 | {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0}, |
| 1727 | 1727 | $td->NORMALIZE_NEWLINES); |
| 1728 | 1728 | } |
| 1729 | +$td->runtest("unfilterable with crypt", | |
| 1730 | + {$td->COMMAND => | |
| 1731 | + "test_driver 36 unfilterable-with-crypt.pdf attachment"}, | |
| 1732 | + {$td->FILE => "unfilterable-with-crypt-before.out", | |
| 1733 | + $td->EXIT_STATUS => 0}, | |
| 1734 | + $td->NORMALIZE_NEWLINES); | |
| 1735 | +unlink "a.pdf"; | |
| 1736 | +$td->runtest("decrypt file", | |
| 1737 | + {$td->COMMAND => "qpdf -decrypt --password=attachment" . | |
| 1738 | + " unfilterable-with-crypt.pdf a.pdf"}, | |
| 1739 | + {$td->STRING => "", $td->EXIT_STATUS => 0}); | |
| 1740 | +$td->runtest("copy of unfilterable with crypt", | |
| 1741 | + {$td->COMMAND => | |
| 1742 | + "test_driver 36 a.pdf attachment"}, | |
| 1743 | + {$td->FILE => "unfilterable-with-crypt-after.out", | |
| 1744 | + $td->EXIT_STATUS => 0}, | |
| 1745 | + $td->NORMALIZE_NEWLINES); | |
| 1729 | 1746 | |
| 1730 | 1747 | show_ntests(); |
| 1731 | 1748 | # ---------- | ... | ... |
qpdf/qtest/qpdf/unfilterable-with-crypt-after.out
0 โ 100644
| 1 | +<< /DL 30 /DecodeParms [ null ] /Filter [ /ZlateDecode ] /Length 39 /Params << /CheckSum <c4f73a3ba2b5fef86a4085d6f006eacd> /CreationDate (D:20121229172641-05'00') /ModDate (D:20121229172600) /Size 30 >> /Subtype /text#2fplain >>attachment1.txt: | |
| 2 | +This is the first attachment. | |
| 3 | +--END-- | |
| 4 | +test 36 done | ... | ... |
qpdf/qtest/qpdf/unfilterable-with-crypt-before.out
0 โ 100644
| 1 | +<< /DL 30 /DecodeParms [ << /Name /StdCF >> null ] /Filter [ /Crypt /ZlateDecode ] /Length 64 /Params << /CheckSum <c4f73a3ba2b5fef86a4085d6f006eacd> /CreationDate (D:20121229172641-05'00') /ModDate (D:20121229172600) /Size 30 >> /Subtype /text#2fplain >>attachment1.txt: | |
| 2 | +This is the first attachment. | |
| 3 | +--END-- | |
| 4 | +test 36 done | ... | ... |
qpdf/qtest/qpdf/unfilterable-with-crypt.pdf
0 โ 100644
No preview for this file type
qpdf/test_driver.cc
| ... | ... | @@ -112,7 +112,7 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 112 | 112 | { |
| 113 | 113 | pdf.setAttemptRecovery(false); |
| 114 | 114 | } |
| 115 | - if ((n == 35) && (arg2 != 0)) | |
| 115 | + if (((n == 35) || (n == 36)) && (arg2 != 0)) | |
| 116 | 116 | { |
| 117 | 117 | // arg2 is password |
| 118 | 118 | pdf.processFile(filename1, arg2); |
| ... | ... | @@ -1214,6 +1214,37 @@ void runtest(int n, char const* filename1, char const* arg2) |
| 1214 | 1214 | std::cout << filename << ":\n" << data << "--END--\n"; |
| 1215 | 1215 | } |
| 1216 | 1216 | } |
| 1217 | + else if (n == 36) | |
| 1218 | + { | |
| 1219 | + // Extract raw unfilterable attachment | |
| 1220 | + | |
| 1221 | + QPDFObjectHandle root = pdf.getRoot(); | |
| 1222 | + QPDFObjectHandle names = root.getKey("/Names"); | |
| 1223 | + QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles"); | |
| 1224 | + names = embeddedFiles.getKey("/Names"); | |
| 1225 | + for (int i = 0; i < names.getArrayNItems(); ++i) | |
| 1226 | + { | |
| 1227 | + QPDFObjectHandle item = names.getArrayItem(i); | |
| 1228 | + if (item.isDictionary() && | |
| 1229 | + item.getKey("/Type").isName() && | |
| 1230 | + (item.getKey("/Type").getName() == "/Filespec") && | |
| 1231 | + item.getKey("/EF").isDictionary() && | |
| 1232 | + item.getKey("/EF").getKey("/F").isStream() && | |
| 1233 | + (item.getKey("/F").getStringValue() == "attachment1.txt")) | |
| 1234 | + { | |
| 1235 | + std::string filename = item.getKey("/F").getStringValue(); | |
| 1236 | + QPDFObjectHandle stream = item.getKey("/EF").getKey("/F"); | |
| 1237 | + Pl_Buffer p1("buffer"); | |
| 1238 | + Pl_Flate p2("compress", &p1, Pl_Flate::a_inflate); | |
| 1239 | + stream.pipeStreamData(&p2, false, false, false); | |
| 1240 | + PointerHolder<Buffer> buf = p1.getBuffer(); | |
| 1241 | + std::string data = std::string( | |
| 1242 | + (char const*)buf->getBuffer(), buf->getSize()); | |
| 1243 | + std::cout << stream.getDict().unparse() | |
| 1244 | + << filename << ":\n" << data << "--END--\n"; | |
| 1245 | + } | |
| 1246 | + } | |
| 1247 | + } | |
| 1217 | 1248 | else |
| 1218 | 1249 | { |
| 1219 | 1250 | throw std::runtime_error(std::string("invalid test ") + | ... | ... |