From 9a23c3dcb675a62e6a8b144304bdc91cae365c4c Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Sun, 30 Dec 2012 13:54:12 -0500 Subject: 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. --- libqpdf/QPDFWriter.cc | 43 ++++++++++++++++++++- qpdf/qpdf.testcov | 1 + qpdf/qtest/qpdf.test | 21 +++++++++- qpdf/qtest/qpdf/unfilterable-with-crypt-after.out | 4 ++ qpdf/qtest/qpdf/unfilterable-with-crypt-before.out | 4 ++ qpdf/qtest/qpdf/unfilterable-with-crypt.pdf | Bin 0 -> 17868 bytes qpdf/test_driver.cc | 33 +++++++++++++++- 7 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 qpdf/qtest/qpdf/unfilterable-with-crypt-after.out create mode 100644 qpdf/qtest/qpdf/unfilterable-with-crypt-before.out create mode 100644 qpdf/qtest/qpdf/unfilterable-with-crypt.pdf diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index d7ab0c8b..ba37d423 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -1297,12 +1297,53 @@ QPDFWriter::unparseObject(QPDFObjectHandle object, int level, // Suppress /Length since we will write it manually object.removeKey("/Length"); - // XXX BUG: /Crypt filters should always be removed. if (flags & f_filtered) { + // We will supply our own filter and decode + // parameters. object.removeKey("/Filter"); object.removeKey("/DecodeParms"); } + else + { + // Make sure, no matter what else we have, that we + // don't have /Crypt in the output filters. + QPDFObjectHandle filter = object.getKey("/Filter"); + QPDFObjectHandle decode_parms = object.getKey("/DecodeParms"); + if (filter.isOrHasName("/Crypt")) + { + if (filter.isName()) + { + object.removeKey("/Filter"); + object.removeKey("/DecodeParms"); + } + else + { + int idx = -1; + for (int i = 0; i < filter.getArrayNItems(); ++i) + { + QPDFObjectHandle item = filter.getArrayItem(i); + if (item.isName() && item.getName() == "/Crypt") + { + idx = i; + break; + } + } + if (idx >= 0) + { + // If filter is an array, then the code in + // QPDF_Stream has already verified that + // DecodeParms and Filters are arrays of + // the same length, but if they weren't + // for some reason, eraseItem does type + // and bounds checking. + QTC::TC("qpdf", "QPDFWriter remove Crypt"); + filter.eraseItem(idx); + decode_parms.eraseItem(idx); + } + } + } + } } writeString("<<"); diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 4ade4e7e..0263f312 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -255,3 +255,4 @@ QPDFWriter preserve ADBE 0 QPDF_encryption skip 0x28 0 QPDF_encrypt crypt array 0 QPDF_encryption CFM AESV3 0 +QPDFWriter remove Crypt 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 47afbcf2..0e164be7 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -1701,12 +1701,12 @@ my @attachments = ( 'enc-XI-attachments-base.pdf', 'enc-XI-R6,V5,U=attachment,encrypted-attachments.pdf', 'enc-XI-R6,V5,U=view,attachments,cleartext-metadata.pdf'); -$n_tests += 4 * @attachments; +$n_tests += 4 * @attachments + 3; foreach my $f (@attachments) { my $pass = ''; my $tpass = ''; - if ($f =~ m/U=([^,]+)/) + if ($f =~ m/U=([^,\.]+)/) { $pass = "--password=$1"; $tpass = $1; @@ -1726,6 +1726,23 @@ foreach my $f (@attachments) {$td->FILE => "attachments.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); } +$td->runtest("unfilterable with crypt", + {$td->COMMAND => + "test_driver 36 unfilterable-with-crypt.pdf attachment"}, + {$td->FILE => "unfilterable-with-crypt-before.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +unlink "a.pdf"; +$td->runtest("decrypt file", + {$td->COMMAND => "qpdf -decrypt --password=attachment" . + " unfilterable-with-crypt.pdf a.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}); +$td->runtest("copy of unfilterable with crypt", + {$td->COMMAND => + "test_driver 36 a.pdf attachment"}, + {$td->FILE => "unfilterable-with-crypt-after.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); show_ntests(); # ---------- diff --git a/qpdf/qtest/qpdf/unfilterable-with-crypt-after.out b/qpdf/qtest/qpdf/unfilterable-with-crypt-after.out new file mode 100644 index 00000000..ac523138 --- /dev/null +++ b/qpdf/qtest/qpdf/unfilterable-with-crypt-after.out @@ -0,0 +1,4 @@ +<< /DL 30 /DecodeParms [ null ] /Filter [ /ZlateDecode ] /Length 39 /Params << /CheckSum /CreationDate (D:20121229172641-05'00') /ModDate (D:20121229172600) /Size 30 >> /Subtype /text#2fplain >>attachment1.txt: +This is the first attachment. +--END-- +test 36 done diff --git a/qpdf/qtest/qpdf/unfilterable-with-crypt-before.out b/qpdf/qtest/qpdf/unfilterable-with-crypt-before.out new file mode 100644 index 00000000..36e2852d --- /dev/null +++ b/qpdf/qtest/qpdf/unfilterable-with-crypt-before.out @@ -0,0 +1,4 @@ +<< /DL 30 /DecodeParms [ << /Name /StdCF >> null ] /Filter [ /Crypt /ZlateDecode ] /Length 64 /Params << /CheckSum /CreationDate (D:20121229172641-05'00') /ModDate (D:20121229172600) /Size 30 >> /Subtype /text#2fplain >>attachment1.txt: +This is the first attachment. +--END-- +test 36 done diff --git a/qpdf/qtest/qpdf/unfilterable-with-crypt.pdf b/qpdf/qtest/qpdf/unfilterable-with-crypt.pdf new file mode 100644 index 00000000..970ea536 Binary files /dev/null and b/qpdf/qtest/qpdf/unfilterable-with-crypt.pdf differ diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 45190042..239464ce 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -112,7 +112,7 @@ void runtest(int n, char const* filename1, char const* arg2) { pdf.setAttemptRecovery(false); } - if ((n == 35) && (arg2 != 0)) + if (((n == 35) || (n == 36)) && (arg2 != 0)) { // arg2 is password pdf.processFile(filename1, arg2); @@ -1214,6 +1214,37 @@ void runtest(int n, char const* filename1, char const* arg2) std::cout << filename << ":\n" << data << "--END--\n"; } } + else if (n == 36) + { + // Extract raw unfilterable attachment + + QPDFObjectHandle root = pdf.getRoot(); + QPDFObjectHandle names = root.getKey("/Names"); + QPDFObjectHandle embeddedFiles = names.getKey("/EmbeddedFiles"); + names = embeddedFiles.getKey("/Names"); + for (int i = 0; i < names.getArrayNItems(); ++i) + { + QPDFObjectHandle item = names.getArrayItem(i); + if (item.isDictionary() && + item.getKey("/Type").isName() && + (item.getKey("/Type").getName() == "/Filespec") && + item.getKey("/EF").isDictionary() && + item.getKey("/EF").getKey("/F").isStream() && + (item.getKey("/F").getStringValue() == "attachment1.txt")) + { + std::string filename = item.getKey("/F").getStringValue(); + QPDFObjectHandle stream = item.getKey("/EF").getKey("/F"); + Pl_Buffer p1("buffer"); + Pl_Flate p2("compress", &p1, Pl_Flate::a_inflate); + stream.pipeStreamData(&p2, false, false, false); + PointerHolder buf = p1.getBuffer(); + std::string data = std::string( + (char const*)buf->getBuffer(), buf->getSize()); + std::cout << stream.getDict().unparse() + << filename << ":\n" << data << "--END--\n"; + } + } + } else { throw std::runtime_error(std::string("invalid test ") + -- cgit v1.2.3-54-g00ecf