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,12 +1297,53 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, | ||
| 1297 | // Suppress /Length since we will write it manually | 1297 | // Suppress /Length since we will write it manually |
| 1298 | object.removeKey("/Length"); | 1298 | object.removeKey("/Length"); |
| 1299 | 1299 | ||
| 1300 | - // XXX BUG: /Crypt filters should always be removed. | ||
| 1301 | if (flags & f_filtered) | 1300 | if (flags & f_filtered) |
| 1302 | { | 1301 | { |
| 1302 | + // We will supply our own filter and decode | ||
| 1303 | + // parameters. | ||
| 1303 | object.removeKey("/Filter"); | 1304 | object.removeKey("/Filter"); |
| 1304 | object.removeKey("/DecodeParms"); | 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 | writeString("<<"); | 1349 | writeString("<<"); |
qpdf/qpdf.testcov
| @@ -255,3 +255,4 @@ QPDFWriter preserve ADBE 0 | @@ -255,3 +255,4 @@ QPDFWriter preserve ADBE 0 | ||
| 255 | QPDF_encryption skip 0x28 0 | 255 | QPDF_encryption skip 0x28 0 |
| 256 | QPDF_encrypt crypt array 0 | 256 | QPDF_encrypt crypt array 0 |
| 257 | QPDF_encryption CFM AESV3 0 | 257 | QPDF_encryption CFM AESV3 0 |
| 258 | +QPDFWriter remove Crypt 0 |
qpdf/qtest/qpdf.test
| @@ -1701,12 +1701,12 @@ my @attachments = ( | @@ -1701,12 +1701,12 @@ my @attachments = ( | ||
| 1701 | 'enc-XI-attachments-base.pdf', | 1701 | 'enc-XI-attachments-base.pdf', |
| 1702 | 'enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf', | 1702 | 'enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf', |
| 1703 | 'enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf'); | 1703 | 'enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf'); |
| 1704 | -$n_tests += 4 * @attachments; | 1704 | +$n_tests += 4 * @attachments + 3; |
| 1705 | foreach my $f (@attachments) | 1705 | foreach my $f (@attachments) |
| 1706 | { | 1706 | { |
| 1707 | my $pass = ''; | 1707 | my $pass = ''; |
| 1708 | my $tpass = ''; | 1708 | my $tpass = ''; |
| 1709 | - if ($f =~ m/U=([^,]+)/) | 1709 | + if ($f =~ m/U=([^,\.]+)/) |
| 1710 | { | 1710 | { |
| 1711 | $pass = "--password=$1"; | 1711 | $pass = "--password=$1"; |
| 1712 | $tpass = $1; | 1712 | $tpass = $1; |
| @@ -1726,6 +1726,23 @@ foreach my $f (@attachments) | @@ -1726,6 +1726,23 @@ foreach my $f (@attachments) | ||
| 1726 | {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0}, | 1726 | {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0}, |
| 1727 | $td->NORMALIZE_NEWLINES); | 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 | show_ntests(); | 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,7 +112,7 @@ void runtest(int n, char const* filename1, char const* arg2) | ||
| 112 | { | 112 | { |
| 113 | pdf.setAttemptRecovery(false); | 113 | pdf.setAttemptRecovery(false); |
| 114 | } | 114 | } |
| 115 | - if ((n == 35) && (arg2 != 0)) | 115 | + if (((n == 35) || (n == 36)) && (arg2 != 0)) |
| 116 | { | 116 | { |
| 117 | // arg2 is password | 117 | // arg2 is password |
| 118 | pdf.processFile(filename1, arg2); | 118 | pdf.processFile(filename1, arg2); |
| @@ -1214,6 +1214,37 @@ void runtest(int n, char const* filename1, char const* arg2) | @@ -1214,6 +1214,37 @@ void runtest(int n, char const* filename1, char const* arg2) | ||
| 1214 | std::cout << filename << ":\n" << data << "--END--\n"; | 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 | else | 1248 | else |
| 1218 | { | 1249 | { |
| 1219 | throw std::runtime_error(std::string("invalid test ") + | 1250 | throw std::runtime_error(std::string("invalid test ") + |