Commit 9a23c3dcb675a62e6a8b144304bdc91cae365c4c

Authored by Jay Berkenbilt
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.
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
... ... @@ -255,3 +255,4 @@ QPDFWriter preserve ADBE 0
255 255 QPDF_encryption skip 0x28 0
256 256 QPDF_encrypt crypt array 0
257 257 QPDF_encryption CFM AESV3 0
  258 +QPDFWriter remove Crypt 0
... ...
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 ") +
... ...