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.
This commit is contained in:
Jay Berkenbilt 2012-12-30 13:54:12 -05:00
parent 4237a29c94
commit 9a23c3dcb6
7 changed files with 102 additions and 4 deletions

View File

@ -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("<<");

View File

@ -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

View File

@ -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();
# ----------

View File

@ -0,0 +1,4 @@
<< /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:
This is the first attachment.
--END--
test 36 done

View File

@ -0,0 +1,4 @@
<< /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:
This is the first attachment.
--END--
test 36 done

Binary file not shown.

View File

@ -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<Buffer> 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 ") +