diff --git a/ChangeLog b/ChangeLog index 5b150c27..35b78e91 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,8 @@ 2012-07-14 Jay Berkenbilt + * QPDFWriter: add public copyEncryptionParameters to allow copying + encryption parameters from another file. + * QPDFWriter: detect if the user has inserted an indirect object from another QPDF object and throw an exception directing the user to copyForeignObject. diff --git a/TODO b/TODO index b29559ee..d38cb002 100644 --- a/TODO +++ b/TODO @@ -64,10 +64,9 @@ Next - Tests through qpdf command line: copy pages from multiple PDFs starting with one PDF and also starting with empty. - * (Hopefully) Provide an option to copy encryption parameters from - another file. This would make it possible to decrypt a file, - manually work with it, and then re-encrypt it using the original - encryption parameters including a possibly unknown owner password. + * qpdf commandline: provide an option to copy encryption parameters + from another file, specifying file and password. Search for "Copy + encryption parameters" in qpdf.test. Soon diff --git a/include/qpdf/QPDFWriter.hh b/include/qpdf/QPDFWriter.hh index 2261e45b..a17b91f4 100644 --- a/include/qpdf/QPDFWriter.hh +++ b/include/qpdf/QPDFWriter.hh @@ -188,6 +188,11 @@ class QPDFWriter QPDF_DLL void setPreserveEncryption(bool); + // Copy encryption parameters from another QPDF object. If you + // want to copy encryption from the object you are writing, call + // setPreserveEncryption(true) instead. + void copyEncryptionParameters(QPDF&); + // Set up for encrypted output. Disables stream prefiltering and // content normalization. Note that setting R2 encryption // parameters sets the PDF version to at least 1.3, setting R3 @@ -269,7 +274,6 @@ class QPDFWriter int V, int R, int key_len, long P, std::string const& O, std::string const& U, std::string const& id1, std::string const& user_password); - void copyEncryptionParameters(); void setDataKey(int objid); int openObject(int objid = 0); void closeObject(int objid); diff --git a/libqpdf/QPDFWriter.cc b/libqpdf/QPDFWriter.cc index ce5cb3e0..96f3ff21 100644 --- a/libqpdf/QPDFWriter.cc +++ b/libqpdf/QPDFWriter.cc @@ -389,10 +389,11 @@ QPDFWriter::setEncryptionParameters( } void -QPDFWriter::copyEncryptionParameters() +QPDFWriter::copyEncryptionParameters(QPDF& qpdf) { + this->preserve_encryption = false; generateID(); - QPDFObjectHandle trailer = this->pdf.getTrailer(); + QPDFObjectHandle trailer = qpdf.getTrailer(); if (trailer.hasKey("/Encrypt")) { QPDFObjectHandle encrypt = trailer.getKey("/Encrypt"); @@ -410,6 +411,8 @@ QPDFWriter::copyEncryptionParameters() } QTC::TC("qpdf", "QPDFWriter copy encrypt metadata", this->encrypt_metadata ? 0 : 1); + this->id1 = + trailer.getKey("/ID").getArrayItem(0).getStringValue(); setEncryptionParametersInternal( V, encrypt.getKey("/R").getIntValue(), @@ -1625,7 +1628,7 @@ QPDFWriter::write() if (preserve_encryption) { - copyEncryptionParameters(); + copyEncryptionParameters(this->pdf); } if (! this->forced_pdf_version.empty()) diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 46a786cc..8687d713 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -1369,6 +1369,23 @@ $td->runtest("check output", {$td->FILE => 'a.pdf'}, {$td->FILE => 'decrypted-crypt-filter.pdf'}); +# Copy encryption parameters +$n_tests += 3; +$td->runtest("create encrypted file", + {$td->COMMAND => + "qpdf --encrypt user owner 128 --use-aes=y --extract=n --" . + " minimal.pdf a.pdf"}, + {$td->STRING => "", $td->EXIT_STATUS => 0}); +$td->runtest("copy encryption parameters", + {$td->COMMAND => "test_driver 30 minimal.pdf a.pdf"}, + {$td->STRING => "test 30 done\n", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); +$td->runtest("checkout encryption", + {$td->COMMAND => "qpdf --show-encryption b.pdf --password=owner"}, + {$td->FILE => "copied-encryption.out", + $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + show_ntests(); # ---------- $td->notify("--- Content Preservation Tests ---"); diff --git a/qpdf/qtest/qpdf/copied-encryption.out b/qpdf/qtest/qpdf/copied-encryption.out new file mode 100644 index 00000000..6db6fa63 --- /dev/null +++ b/qpdf/qtest/qpdf/copied-encryption.out @@ -0,0 +1,12 @@ +R = 4 +P = -20 +User password = user +extract for accessibility: allowed +extract for any purpose: not allowed +print low resolution: allowed +print high resolution: allowed +modify document assembly: allowed +modify forms: allowed +modify annotations: allowed +modify other: allowed +modify anything: allowed diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index 0d1ecbb2..518e5569 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -1024,6 +1024,17 @@ void runtest(int n, char const* filename1, char const* filename2) std::cout << "logic error: " << e.what() << std::endl; } } + else if (n == 30) + { + assert(filename2 != 0); + QPDF encrypted; + encrypted.processFile(filename2, "user"); + QPDFWriter w(pdf, "b.pdf"); + w.setStaticID(true); + w.setStreamDataMode(qpdf_s_preserve); + w.copyEncryptionParameters(encrypted); + w.write(); + } else { throw std::runtime_error(std::string("invalid test ") +