aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO26
-rw-r--r--include/qpdf/QPDF.hh7
-rw-r--r--libqpdf/QPDF.cc48
-rw-r--r--libqpdf/QPDF_encryption.cc25
-rw-r--r--qpdf/qpdf.testcov1
-rw-r--r--qpdf/qtest/qpdf.test17
-rw-r--r--qpdf/qtest/qpdf/indirect-r-arg.out2
-rw-r--r--qpdf/qtest/qpdf/nontrivial-crypt-filter-decrypted.pdfbin0 -> 9309 bytes
-rw-r--r--qpdf/qtest/qpdf/nontrivial-crypt-filter.out17
-rw-r--r--qpdf/qtest/qpdf/nontrivial-crypt-filter.pdfbin0 -> 8985 bytes
-rw-r--r--qpdf/qtest/qpdf/obj0-check.out2
-rw-r--r--qpdf/qtest/qpdf/test73.out4
12 files changed, 64 insertions, 85 deletions
diff --git a/TODO b/TODO
index a63af638..d8e262fc 100644
--- a/TODO
+++ b/TODO
@@ -425,25 +425,13 @@ I find it useful to make reference to them in this list.
http://multivalent.sourceforge.net/Tools/pdf/Extract.html
http://multivalent.sourceforge.net/Tools/pdf/Embed.html
- * The description of Crypt filters is unclear with respect to how to
- use them to override /StmF for specific streams. I'm not sure
- whether qpdf will do the right thing for any specific individual
- streams that might have crypt filters, but I believe it does based
- on my testing of a limited subset. The specification seems to imply
- that only embedded file streams and metadata streams can have crypt
- filters, and there are already special cases in the code to handle
- those. Most likely, it won't be a problem, but someday someone may
- find a file that qpdf doesn't work on because of crypt filters.
- There is an example in the spec of using a crypt filter on a
- metadata stream.
-
- For now, we notice /Crypt filters and decode parameters consistent
- with the example in the PDF specification, and the right thing
- happens for metadata filters that happen to be uncompressed or
- otherwise compressed in a way we can filter. This should handle
- all normal cases, but it's more or less just a guess since I don't
- have any test files that actually use stream-specific crypt filters
- in them.
+ * Qpdf does not honor /EFF when adding new file attachments. When it
+ encrypts, it never generates streams with explicit crypt filters.
+ Prior to 10.2, there was an incorrect attempt to treat /EFF as a
+ default value for decrypting file attachment streams, but it is not
+ supposed to mean that. Instead, it is intended for comforming
+ writers to obey this when adding new attachments. Qpdf is not a
+ conforming writer in that respect.
* The second xref stream for linearized files has to be padded only
because we need file_size as computed in pass 1 to be accurate. If
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index 4e0984a7..4490189d 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -814,7 +814,6 @@ class QPDF
int foreign_generation,
qpdf_offset_t offset,
size_t length,
- bool is_attachment_stream,
QPDFObjectHandle local_dict);
private:
@@ -824,7 +823,6 @@ class QPDF
int foreign_generation;
qpdf_offset_t offset;
size_t length;
- bool is_attachment_stream;
QPDFObjectHandle local_dict;
};
@@ -919,7 +917,6 @@ class QPDF
int& act_objid, int& act_generation);
PointerHolder<QPDFObject> resolve(int objid, int generation);
void resolveObjectsInStream(int obj_stream_number);
- void findAttachmentStreams();
void stopOnError(std::string const& message);
// Calls finish() on the pipeline when done but does not delete it
@@ -938,7 +935,6 @@ class QPDF
int objid, int generation,
qpdf_offset_t offset, size_t length,
QPDFObjectHandle dict,
- bool is_attachment_stream,
Pipeline* pipeline,
bool suppress_warnings,
bool will_retry);
@@ -1001,7 +997,7 @@ class QPDF
PointerHolder<InputSource> file,
QPDF& qpdf_for_warning, Pipeline*& pipeline,
int objid, int generation,
- QPDFObjectHandle& stream_dict, bool is_attachment_stream,
+ QPDFObjectHandle& stream_dict,
std::vector<PointerHolder<Pipeline> >& heap);
// Methods to support object copying
@@ -1422,7 +1418,6 @@ class QPDF
PointerHolder<QPDFObjectHandle::StreamDataProvider> copied_streams;
// copied_stream_data_provider is owned by copied_streams
CopiedStreamDataProvider* copied_stream_data_provider;
- std::set<QPDFObjGen> attachment_streams;
bool reconstructed_xref;
bool fixed_dangling_refs;
bool immediate_copy_from;
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index 89c6ed74..3209903f 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -18,7 +18,6 @@
#include <qpdf/FileInputSource.hh>
#include <qpdf/BufferInputSource.hh>
#include <qpdf/OffsetInputSource.hh>
-#include <qpdf/QPDFNameTreeObjectHelper.hh>
#include <qpdf/QPDFExc.hh>
#include <qpdf/QPDF_Null.hh>
@@ -98,7 +97,6 @@ QPDF::ForeignStreamData::ForeignStreamData(
int foreign_generation,
qpdf_offset_t offset,
size_t length,
- bool is_attachment_stream,
QPDFObjectHandle local_dict)
:
encp(encp),
@@ -107,7 +105,6 @@ QPDF::ForeignStreamData::ForeignStreamData(
foreign_generation(foreign_generation),
offset(offset),
length(length),
- is_attachment_stream(is_attachment_stream),
local_dict(local_dict)
{
}
@@ -508,7 +505,6 @@ QPDF::parse(char const* password)
}
initializeEncryption();
- findAttachmentStreams();
this->m->parsed = true;
}
@@ -2648,8 +2644,6 @@ QPDF::replaceForeignIndirectObjects(
foreign.getGeneration(),
stream->getOffset(),
stream->getLength(),
- (foreign_stream_qpdf->m->attachment_streams.count(
- foreign.getObjGen()) > 0),
dict);
this->m->copied_stream_data_provider->registerForeignStream(
local_og, foreign_stream_data);
@@ -2882,7 +2876,6 @@ QPDF::pipeStreamData(PointerHolder<EncryptionParameters> encp,
int objid, int generation,
qpdf_offset_t offset, size_t length,
QPDFObjectHandle stream_dict,
- bool is_attachment_stream,
Pipeline* pipeline,
bool suppress_warnings,
bool will_retry)
@@ -2892,7 +2885,7 @@ QPDF::pipeStreamData(PointerHolder<EncryptionParameters> encp,
{
decryptStream(encp, file, qpdf_for_warning,
pipeline, objid, generation,
- stream_dict, is_attachment_stream, to_delete);
+ stream_dict, to_delete);
}
bool success = false;
@@ -2968,14 +2961,11 @@ QPDF::pipeStreamData(int objid, int generation,
bool suppress_warnings,
bool will_retry)
{
- bool is_attachment_stream = (
- this->m->attachment_streams.count(
- QPDFObjGen(objid, generation)) > 0);
return pipeStreamData(
this->m->encp, this->m->file, *this,
objid, generation, offset, length,
- stream_dict, is_attachment_stream,
- pipeline, suppress_warnings, will_retry);
+ stream_dict, pipeline,
+ suppress_warnings, will_retry);
}
bool
@@ -2992,36 +2982,8 @@ QPDF::pipeForeignStreamData(
foreign->encp, foreign->file, *this,
foreign->foreign_objid, foreign->foreign_generation,
foreign->offset, foreign->length,
- foreign->local_dict, foreign->is_attachment_stream,
- pipeline, suppress_warnings, will_retry);
-}
-
-void
-QPDF::findAttachmentStreams()
-{
- QPDFObjectHandle root = getRoot();
- QPDFObjectHandle names = root.getKey("/Names");
- if (! names.isDictionary())
- {
- return;
- }
- QPDFObjectHandle embedded_files = names.getKey("/EmbeddedFiles");
- if (! embedded_files.isDictionary())
- {
- return;
- }
- QPDFNameTreeObjectHelper ef_tree(embedded_files, *this);
- for (auto const& i: ef_tree)
- {
- QPDFObjectHandle item = i.second;
- if (item.isDictionary() &&
- item.getKey("/EF").isDictionary() &&
- item.getKey("/EF").getKey("/F").isStream())
- {
- QPDFObjectHandle stream = item.getKey("/EF").getKey("/F");
- this->m->attachment_streams.insert(stream.getObjGen());
- }
- }
+ foreign->local_dict, pipeline,
+ suppress_warnings, will_retry);
}
void
diff --git a/libqpdf/QPDF_encryption.cc b/libqpdf/QPDF_encryption.cc
index 00bd697e..2ff48df9 100644
--- a/libqpdf/QPDF_encryption.cc
+++ b/libqpdf/QPDF_encryption.cc
@@ -1022,6 +1022,20 @@ QPDF::initializeEncryption()
this->m->encp->cf_string = interpretCF(this->m->encp, StrF);
if (EFF.isName())
{
+ // qpdf does not use this for anything other than
+ // informational purposes. This is intended to instruct
+ // conforming writers on which crypt filter should be used
+ // when new file attachments are added to a PDF file, but
+ // qpdf never generates encrypted files with non-default
+ // crypt filters. Prior to 10.2, I was under the mistaken
+ // impression that this was supposed to be used for
+ // decrypting attachments, but the code was wrong in a way
+ // that turns out not to have mattered because no writers
+ // were generating files the way I was imagining. Still,
+ // providing this information could be useful when looking
+ // at a file generated by something else, such as Acrobat
+ // when specifying that only attachments should be
+ // encrypted.
this->m->encp->cf_file = interpretCF(this->m->encp, EFF);
}
else
@@ -1224,7 +1238,6 @@ QPDF::decryptStream(PointerHolder<EncryptionParameters> encp,
QPDF& qpdf_for_warning, Pipeline*& pipeline,
int objid, int generation,
QPDFObjectHandle& stream_dict,
- bool is_attachment_stream,
std::vector<PointerHolder<Pipeline> >& heap)
{
std::string type;
@@ -1296,15 +1309,7 @@ QPDF::decryptStream(PointerHolder<EncryptionParameters> encp,
}
else
{
- if (is_attachment_stream)
- {
- QTC::TC("qpdf", "QPDF_encryption attachment stream");
- method = encp->cf_file;
- }
- else
- {
- method = encp->cf_stream;
- }
+ method = encp->cf_stream;
}
}
use_aes = false;
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index 7f01ae0b..20015780 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -401,7 +401,6 @@ qpdf image optimize no pipeline 0
qpdf image optimize no shrink 0
qpdf image optimize too small 0
QPDFFormFieldObjectHelper WinAnsi 0
-QPDF_encryption attachment stream 0
QPDF pipe foreign encrypted stream 0
QPDF copy foreign stream with provider 0
QPDF copy foreign stream with buffer 0
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 13e7ab3e..45600db9 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -1257,7 +1257,7 @@ $n_tests += 3;
$td->runtest("closed input source",
{$td->COMMAND => "test_driver 73 minimal.pdf"},
{$td->FILE => "test73.out",
- $td->EXIT_STATUS => 0},
+ $td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("empty object",
@@ -3878,7 +3878,7 @@ $td->runtest("check encryption",
$td->NORMALIZE_NEWLINES);
# Look at some actual V4 files
-$n_tests += 14;
+$n_tests += 17;
foreach my $d (['--force-V4', 'V4'],
['--cleartext-metadata', 'V4-clearmeta'],
['--use-aes=y', 'V4-aes'],
@@ -3906,6 +3906,19 @@ $td->runtest("decrypt with crypt filter",
$td->runtest("check output",
{$td->FILE => 'a.pdf'},
{$td->FILE => 'decrypted-crypt-filter.pdf'});
+$td->runtest("nontrivial crypt filter",
+ {$td->COMMAND => "qpdf --qdf --decrypt --static-id" .
+ " nontrivial-crypt-filter.pdf --password=asdfqwer a.pdf"},
+ {$td->STRING => "", $td->EXIT_STATUS => 0});
+$td->runtest("check output",
+ {$td->FILE => 'a.pdf'},
+ {$td->FILE => 'nontrivial-crypt-filter-decrypted.pdf'});
+$td->runtest("show nontrivial EFF",
+ {$td->COMMAND => "qpdf --show-encryption" .
+ " nontrivial-crypt-filter.pdf --password=asdfqwer"},
+ {$td->FILE => "nontrivial-crypt-filter.out",
+ $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
# Copy encryption parameters
$n_tests += 10;
diff --git a/qpdf/qtest/qpdf/indirect-r-arg.out b/qpdf/qtest/qpdf/indirect-r-arg.out
index 00a7175d..0b8be9e3 100644
--- a/qpdf/qtest/qpdf/indirect-r-arg.out
+++ b/qpdf/qtest/qpdf/indirect-r-arg.out
@@ -1,7 +1,7 @@
+checking indirect-r-arg.pdf
WARNING: indirect-r-arg.pdf (object 1 0, offset 76): unknown token while reading object; treating as string
WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake1
WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but found non-name object; inserting key /QPDFFake2
-checking indirect-r-arg.pdf
PDF Version: 1.3
File is not encrypted
File is not linearized
diff --git a/qpdf/qtest/qpdf/nontrivial-crypt-filter-decrypted.pdf b/qpdf/qtest/qpdf/nontrivial-crypt-filter-decrypted.pdf
new file mode 100644
index 00000000..8f3ac59e
--- /dev/null
+++ b/qpdf/qtest/qpdf/nontrivial-crypt-filter-decrypted.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/nontrivial-crypt-filter.out b/qpdf/qtest/qpdf/nontrivial-crypt-filter.out
new file mode 100644
index 00000000..e16d0008
--- /dev/null
+++ b/qpdf/qtest/qpdf/nontrivial-crypt-filter.out
@@ -0,0 +1,17 @@
+R = 6
+P = -1028
+User password = asdfqwer
+Supplied password is owner password
+Supplied password is user password
+extract for accessibility: allowed
+extract for any purpose: allowed
+print low resolution: allowed
+print high resolution: allowed
+modify document assembly: not allowed
+modify forms: allowed
+modify annotations: allowed
+modify other: allowed
+modify anything: not allowed
+stream encryption method: none
+string encryption method: none
+file encryption method: AESv3
diff --git a/qpdf/qtest/qpdf/nontrivial-crypt-filter.pdf b/qpdf/qtest/qpdf/nontrivial-crypt-filter.pdf
new file mode 100644
index 00000000..ff9653c6
--- /dev/null
+++ b/qpdf/qtest/qpdf/nontrivial-crypt-filter.pdf
Binary files differ
diff --git a/qpdf/qtest/qpdf/obj0-check.out b/qpdf/qtest/qpdf/obj0-check.out
index a9b37544..64cb8d15 100644
--- a/qpdf/qtest/qpdf/obj0-check.out
+++ b/qpdf/qtest/qpdf/obj0-check.out
@@ -1,7 +1,7 @@
+checking obj0.pdf
WARNING: obj0.pdf: file is damaged
WARNING: obj0.pdf (object 1 0, offset 77): expected n n obj
WARNING: obj0.pdf: Attempting to reconstruct cross-reference table
-checking obj0.pdf
PDF Version: 1.3
File is not encrypted
File is not linearized
diff --git a/qpdf/qtest/qpdf/test73.out b/qpdf/qtest/qpdf/test73.out
index 1b7ad0e6..435189fd 100644
--- a/qpdf/qtest/qpdf/test73.out
+++ b/qpdf/qtest/qpdf/test73.out
@@ -1,2 +1,2 @@
-WARNING: closed input source: object 2/0: error reading object: QPDF operation attempted after closing input source
-test 73 done
+WARNING: closed input source: object 1/0: error reading object: QPDF operation attempted after closing input source
+closed input source: unable to find /Root dictionary