diff --git a/ChangeLog b/ChangeLog index c85b27a5..c68c4ceb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,12 @@ +2012-12-31 Jay Berkenbilt + + * Add new methods qpdf_get_pdf_extension_level, + qpdf_set_r5_encryption_parameters, + qpdf_set_r6_encryption_parameters, + qpdf_set_minimum_pdf_version_and_extension, and + qpdf_force_pdf_version_and_extension to support new functionality + from the C API. + 2012-12-30 Jay Berkenbilt * Fix long-standing bug that could theoretically have resulted in diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h index 57100839..ee4fa58c 100644 --- a/include/qpdf/qpdf-c.h +++ b/include/qpdf/qpdf-c.h @@ -213,6 +213,10 @@ extern "C" { QPDF_DLL char const* qpdf_get_pdf_version(qpdf_data qpdf); + /* Return the extension level of the PDF file. */ + QPDF_DLL + int qpdf_get_pdf_extension_level(qpdf_data qpdf); + /* Return the user password. If the file is opened using the * owner password, the user password may be retrieved using this * function. If the file is opened using the user password, this @@ -358,15 +362,37 @@ extern "C" { enum qpdf_r3_print_e print, enum qpdf_r3_modify_e modify, QPDF_BOOL encrypt_metadata, QPDF_BOOL use_aes); + QPDF_DLL + void qpdf_set_r5_encryption_parameters( + qpdf_data qpdf, char const* user_password, char const* owner_password, + QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract, + enum qpdf_r3_print_e print, enum qpdf_r3_modify_e modify, + QPDF_BOOL encrypt_metadata); + + QPDF_DLL + void qpdf_set_r6_encryption_parameters( + qpdf_data qpdf, char const* user_password, char const* owner_password, + QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract, + enum qpdf_r3_print_e print, enum qpdf_r3_modify_e modify, + QPDF_BOOL encrypt_metadata); + QPDF_DLL void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value); QPDF_DLL void qpdf_set_minimum_pdf_version(qpdf_data qpdf, char const* version); + QPDF_DLL + void qpdf_set_minimum_pdf_version_and_extension( + qpdf_data qpdf, char const* version, int extension_level); + QPDF_DLL void qpdf_force_pdf_version(qpdf_data qpdf, char const* version); + QPDF_DLL + void qpdf_force_pdf_version_and_extension( + qpdf_data qpdf, char const* version, int extension_level); + /* Do actual write operation. */ QPDF_DLL QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf); diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc index 0312ae50..a46df63e 100644 --- a/libqpdf/qpdf-c.cc +++ b/libqpdf/qpdf-c.cc @@ -288,6 +288,12 @@ char const* qpdf_get_pdf_version(qpdf_data qpdf) return qpdf->tmp_string.c_str(); } +int qpdf_get_pdf_extension_level(qpdf_data qpdf) +{ + QTC::TC("qpdf", "qpdf-c called qpdf_get_pdf_extension_level"); + return qpdf->qpdf->getExtensionLevel(); +} + char const* qpdf_get_user_password(qpdf_data qpdf) { QTC::TC("qpdf", "qpdf-c called qpdf_get_user_password"); @@ -566,6 +572,32 @@ void qpdf_set_r4_encryption_parameters( encrypt_metadata, use_aes); } +void qpdf_set_r5_encryption_parameters( + qpdf_data qpdf, char const* user_password, char const* owner_password, + QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract, + qpdf_r3_print_e print, qpdf_r3_modify_e modify, + QPDF_BOOL encrypt_metadata) +{ + QTC::TC("qpdf", "qpdf-c called qpdf_set_r5_encryption_parameters"); + qpdf->qpdf_writer->setR5EncryptionParameters( + user_password, owner_password, + allow_accessibility, allow_extract, print, modify, + encrypt_metadata); +} + +void qpdf_set_r6_encryption_parameters( + qpdf_data qpdf, char const* user_password, char const* owner_password, + QPDF_BOOL allow_accessibility, QPDF_BOOL allow_extract, + qpdf_r3_print_e print, qpdf_r3_modify_e modify, + QPDF_BOOL encrypt_metadata) +{ + QTC::TC("qpdf", "qpdf-c called qpdf_set_r6_encryption_parameters"); + qpdf->qpdf_writer->setR6EncryptionParameters( + user_password, owner_password, + allow_accessibility, allow_extract, print, modify, + encrypt_metadata); +} + void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value) { QTC::TC("qpdf", "qpdf-c called qpdf_set_linearization"); @@ -573,15 +605,27 @@ void qpdf_set_linearization(qpdf_data qpdf, QPDF_BOOL value) } void qpdf_set_minimum_pdf_version(qpdf_data qpdf, char const* version) +{ + qpdf_set_minimum_pdf_version_and_extension(qpdf, version, 0); +} + +void qpdf_set_minimum_pdf_version_and_extension( + qpdf_data qpdf, char const* version, int extension_level) { QTC::TC("qpdf", "qpdf-c called qpdf_set_minimum_pdf_version"); - qpdf->qpdf_writer->setMinimumPDFVersion(version); + qpdf->qpdf_writer->setMinimumPDFVersion(version, extension_level); } void qpdf_force_pdf_version(qpdf_data qpdf, char const* version) +{ + qpdf_force_pdf_version_and_extension(qpdf, version, 0); +} + +void qpdf_force_pdf_version_and_extension( + qpdf_data qpdf, char const* version, int extension_level) { QTC::TC("qpdf", "qpdf-c called qpdf_force_pdf_version"); - qpdf->qpdf_writer->forcePDFVersion(version); + qpdf->qpdf_writer->forcePDFVersion(version, extension_level); } QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf) diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c index 072e5a7c..58fcb002 100644 --- a/qpdf/qpdf-ctest.c +++ b/qpdf/qpdf-ctest.c @@ -106,6 +106,10 @@ static void test01(char const* infile, { qpdf_read(qpdf, infile, password); printf("version: %s\n", qpdf_get_pdf_version(qpdf)); + if (qpdf_get_pdf_extension_level(qpdf) > 0) + { + printf("extension level: %d\n", qpdf_get_pdf_extension_level(qpdf)); + } printf("linearized: %d\n", qpdf_is_linearized(qpdf)); printf("encrypted: %d\n", qpdf_is_encrypted(qpdf)); if (qpdf_is_encrypted(qpdf)) @@ -304,7 +308,7 @@ static void test14(char const* infile, qpdf_read(qpdf, infile, password); qpdf_init_write(qpdf, outfile); qpdf_set_static_ID(qpdf, QPDF_TRUE); - qpdf_set_minimum_pdf_version(qpdf, "1.6"); + qpdf_set_minimum_pdf_version_and_extension(qpdf, "1.7", 8); qpdf_write(qpdf); qpdf_init_write(qpdf, outfile2); qpdf_set_static_ID(qpdf, QPDF_TRUE); @@ -374,6 +378,38 @@ static void test16(char const* infile, report_errors(); } +static void test17(char const* infile, + char const* password, + char const* outfile, + char const* outfile2) +{ + qpdf_read(qpdf, infile, password); + qpdf_init_write(qpdf, outfile); + qpdf_set_static_ID(qpdf, QPDF_TRUE); + qpdf_set_static_aes_IV(qpdf, QPDF_TRUE); + qpdf_set_r5_encryption_parameters( + qpdf, "user3", "owner3", QPDF_TRUE, QPDF_TRUE, + qpdf_r3p_low, qpdf_r3m_all, QPDF_TRUE); + qpdf_write(qpdf); + report_errors(); +} + +static void test18(char const* infile, + char const* password, + char const* outfile, + char const* outfile2) +{ + qpdf_read(qpdf, infile, password); + qpdf_init_write(qpdf, outfile); + qpdf_set_static_ID(qpdf, QPDF_TRUE); + qpdf_set_static_aes_IV(qpdf, QPDF_TRUE); + qpdf_set_r6_encryption_parameters( + qpdf, "user4", "owner4", QPDF_TRUE, QPDF_TRUE, + qpdf_r3p_low, qpdf_r3m_all, QPDF_TRUE); + qpdf_write(qpdf); + report_errors(); +} + int main(int argc, char* argv[]) { char* p = 0; @@ -430,6 +466,8 @@ int main(int argc, char* argv[]) (n == 14) ? test14 : (n == 15) ? test15 : (n == 16) ? test16 : + (n == 17) ? test17 : + (n == 18) ? test18 : 0); if (fn == 0) diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 0263f312..a0578f28 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -256,3 +256,6 @@ QPDF_encryption skip 0x28 0 QPDF_encrypt crypt array 0 QPDF_encryption CFM AESV3 0 QPDFWriter remove Crypt 0 +qpdf-c called qpdf_get_pdf_extension_level 0 +qpdf-c called qpdf_set_r5_encryption_parameters 0 +qpdf-c called qpdf_set_r6_encryption_parameters 0 diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test index 0e164be7..bf62ceea 100644 --- a/qpdf/qtest/qpdf.test +++ b/qpdf/qtest/qpdf.test @@ -315,8 +315,8 @@ $td->runtest("C API: min/force versions", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("C check version 1", - {$td->COMMAND => "qpdf --check a.pdf"}, - {$td->FILE => "min-version.out", + {$td->COMMAND => "qpdf-ctest 1 a.pdf '' ''"}, + {$td->FILE => "c-min-version.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); $td->runtest("C check version 2", @@ -1472,29 +1472,52 @@ $td->runtest("C API: invalid password", $td->NORMALIZE_NEWLINES); my @cenc = ( - [11, 'hybrid-xref.pdf', "''", 'r2', ""], - [12, 'hybrid-xref.pdf', "''", 'r3', ""], - [15, 'hybrid-xref.pdf', "''", 'r4', ""], + [11, 'hybrid-xref.pdf', "''", 'r2', "", ""], + [12, 'hybrid-xref.pdf', "''", 'r3', "", ""], + [15, 'hybrid-xref.pdf', "''", 'r4', "", ""], + [17, 'hybrid-xref.pdf', "''", 'r5', "", "owner3"], + [18, 'hybrid-xref.pdf', "''", 'r6', "", "user4"], [13, 'c-r2.pdf', 'user1', 'decrypt with user', - "user password: user1\n"], + "user password: user1\n", ""], [13, 'c-r3.pdf', 'owner2', 'decrypt with owner', - "user password: user2\n"], + "user password: user2\n", ""], + [13, 'c-r5-in.pdf', 'user3', 'decrypt R5 with user', + "user password: user3\n", ""], + [13, 'c-r6-in.pdf', 'owner4', 'decrypt R6 with owner', + "user password: \n", ""], ); $n_tests += 2 * @cenc; foreach my $d (@cenc) { - my ($n, $infile, $pass, $description, $output) = @$d; + my ($n, $infile, $pass, $description, $output, $checkpass) = @$d; my $outfile = $description; $outfile =~ s/ /-/g; - $outfile = "c-$outfile.pdf"; + my $pdf_outfile = "c-$outfile.pdf"; + my $check_outfile = "c-$outfile.out"; $td->runtest("C API encryption: $description", {$td->COMMAND => "qpdf-ctest $n $infile $pass a.pdf"}, {$td->STRING => $output, $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); - $td->runtest("check $description", - {$td->FILE => "a.pdf"}, - {$td->FILE => $outfile}); + if (-f $pdf_outfile) + { + $td->runtest("check $description content", + {$td->FILE => "a.pdf"}, + {$td->FILE => $pdf_outfile}); + } + else + { + # QPDF doesn't provide any way to make the random bits in + # /Perms static, so we have no way to predictably create a + # /V=5 encrypted file. It's not worth adding this...the test + # suite is adequate without having a statically predictable + # file. + $td->runtest("check $description", + {$td->COMMAND => + "qpdf --check a.pdf --password=$checkpass"}, + {$td->FILE => $check_outfile, $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + } } # Test combinations of linearization and encryption. Note that we do diff --git a/qpdf/qtest/qpdf/c-decrypt-R5-with-user.pdf b/qpdf/qtest/qpdf/c-decrypt-R5-with-user.pdf new file mode 100644 index 00000000..e3ff67ee Binary files /dev/null and b/qpdf/qtest/qpdf/c-decrypt-R5-with-user.pdf differ diff --git a/qpdf/qtest/qpdf/c-decrypt-R6-with-owner.pdf b/qpdf/qtest/qpdf/c-decrypt-R6-with-owner.pdf new file mode 100644 index 00000000..8e5d9b81 Binary files /dev/null and b/qpdf/qtest/qpdf/c-decrypt-R6-with-owner.pdf differ diff --git a/qpdf/qtest/qpdf/c-min-version.out b/qpdf/qtest/qpdf/c-min-version.out new file mode 100644 index 00000000..94558ace --- /dev/null +++ b/qpdf/qtest/qpdf/c-min-version.out @@ -0,0 +1,4 @@ +version: 1.7 +extension level: 8 +linearized: 0 +encrypted: 0 diff --git a/qpdf/qtest/qpdf/c-r5-in.pdf b/qpdf/qtest/qpdf/c-r5-in.pdf new file mode 100644 index 00000000..19c67a30 Binary files /dev/null and b/qpdf/qtest/qpdf/c-r5-in.pdf differ diff --git a/qpdf/qtest/qpdf/c-r5.out b/qpdf/qtest/qpdf/c-r5.out new file mode 100644 index 00000000..b8789e22 --- /dev/null +++ b/qpdf/qtest/qpdf/c-r5.out @@ -0,0 +1,20 @@ +checking a.pdf +PDF Version: 1.7 extension level 3 +R = 5 +P = -2052 +User password = +extract for accessibility: allowed +extract for any purpose: allowed +print low resolution: allowed +print high resolution: not allowed +modify document assembly: allowed +modify forms: allowed +modify annotations: allowed +modify other: allowed +modify anything: allowed +stream encryption method: AESv3 +string encryption method: AESv3 +file encryption method: AESv3 +File is not linearized +No syntax or stream encoding errors found; the file may still contain +errors that qpdf cannot detect diff --git a/qpdf/qtest/qpdf/c-r6-in.pdf b/qpdf/qtest/qpdf/c-r6-in.pdf new file mode 100644 index 00000000..d8aa5d67 Binary files /dev/null and b/qpdf/qtest/qpdf/c-r6-in.pdf differ diff --git a/qpdf/qtest/qpdf/c-r6.out b/qpdf/qtest/qpdf/c-r6.out new file mode 100644 index 00000000..1f99b975 --- /dev/null +++ b/qpdf/qtest/qpdf/c-r6.out @@ -0,0 +1,20 @@ +checking a.pdf +PDF Version: 1.7 extension level 8 +R = 6 +P = -2052 +User password = user4 +extract for accessibility: allowed +extract for any purpose: allowed +print low resolution: allowed +print high resolution: not allowed +modify document assembly: allowed +modify forms: allowed +modify annotations: allowed +modify other: allowed +modify anything: allowed +stream encryption method: AESv3 +string encryption method: AESv3 +file encryption method: AESv3 +File is not linearized +No syntax or stream encoding errors found; the file may still contain +errors that qpdf cannot detect