diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc index cd7c2b4..424a9f1 100644 --- a/libqpdf/QPDF.cc +++ b/libqpdf/QPDF.cc @@ -636,8 +636,7 @@ int QPDF::getExtensionLevel() { if (Integer ExtensionLevel = getRoot()["/Extensions"]["/ADBE"]["/ExtensionLevel"]) { - int result = ExtensionLevel; - return result; + return ExtensionLevel.value(); } return 0; } diff --git a/libqpdf/QPDFEFStreamObjectHelper.cc b/libqpdf/QPDFEFStreamObjectHelper.cc index 3e61673..61c929c 100644 --- a/libqpdf/QPDFEFStreamObjectHelper.cc +++ b/libqpdf/QPDFEFStreamObjectHelper.cc @@ -59,8 +59,7 @@ size_t QPDFEFStreamObjectHelper::getSize() { if (Integer Size = getParam("/Size")) { - size_t result = Size; - return result; + return Size.value(); } return 0; } diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 7770df6..0d820a6 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -852,13 +852,7 @@ int QPDFObjectHandle::getIntValueAsInt() const { try { - return Integer(*this); - } catch (std::underflow_error&) { - warn("requested value of integer is too small; returning INT_MIN"); - return INT_MIN; - } catch (std::overflow_error&) { - warn("requested value of integer is too big; returning INT_MAX"); - return INT_MAX; + return Integer(*this).value(); } catch (std::invalid_argument&) { typeWarning("integer", "returning 0"); return 0; @@ -879,10 +873,7 @@ unsigned long long QPDFObjectHandle::getUIntValue() const { try { - return Integer(*this); - } catch (std::underflow_error&) { - warn("unsigned value request for negative number; returning 0"); - return 0; + return Integer(*this).value(); } catch (std::invalid_argument&) { typeWarning("integer", "returning 0"); return 0; @@ -903,13 +894,7 @@ unsigned int QPDFObjectHandle::getUIntValueAsUInt() const { try { - return Integer(*this); - } catch (std::underflow_error&) { - warn("unsigned integer value request for negative number; returning 0"); - return 0; - } catch (std::overflow_error&) { - warn("requested value of unsigned integer is too big; returning UINT_MAX"); - return UINT_MAX; + return Integer(*this).value(); } catch (std::invalid_argument&) { typeWarning("integer", "returning 0"); return 0; diff --git a/libqpdf/QPDF_linearization.cc b/libqpdf/QPDF_linearization.cc index d5522ad..f9ed6ed 100644 --- a/libqpdf/QPDF_linearization.cc +++ b/libqpdf/QPDF_linearization.cc @@ -487,20 +487,20 @@ Lin::readLinearizationData() ); // file_size initialized by isLinearized() - linp_.first_page_object = O; + linp_.first_page_object = O.value(); linp_.first_page_end = E; - linp_.npages = N; + linp_.npages = N.value(); linp_.xref_zero_offset = T; - linp_.first_page = P ? P : 0; + linp_.first_page = P ? P.value() : 0; linp_.H_offset = H_0; linp_.H_length = H_1; // Read hint streams Pl_Buffer pb("hint buffer"); - auto H0 = readHintStream(pb, H_0, H_1); + auto H0 = readHintStream(pb, H_0, H_1.value()); if (H_2) { - (void)readHintStream(pb, H_2, H_3); + (void)readHintStream(pb, H_2, H_3.value()); } // PDF 1.4 hint tables that we ignore: @@ -524,7 +524,7 @@ Lin::readLinearizationData() readHPageOffset(BitStream(h_buf, h_size)); - size_t HSi = HS; + size_t HSi = HS.value(); if (HSi < 0 || HSi >= h_size) { throw damagedPDF("linearization hint table", "/S (shared object) offset is out of bounds"); } @@ -536,7 +536,7 @@ Lin::readLinearizationData() "/O (outline) offset is out of bounds", "linearization dictionary" // ); - size_t HOi = HO; + size_t HOi = HO.value(); readHGeneric(BitStream(h_buf + HO, h_size - HOi), outline_hints_); } } diff --git a/libqpdf/qpdf/QPDFObjectHandle_private.hh b/libqpdf/qpdf/QPDFObjectHandle_private.hh index 0f478a8..cccbd7d 100644 --- a/libqpdf/qpdf/QPDFObjectHandle_private.hh +++ b/libqpdf/qpdf/QPDFObjectHandle_private.hh @@ -345,6 +345,42 @@ namespace qpdf // std::invalid_argument exception. int64_t value() const; + // Return the integer value. If the object is not a valid integer, throw a + // std::invalid_argument exception. If the object is out of range for the target type, + // replicate the existing QPDFObjectHandle behavior. + template + T + value() const + { + try { + return static_cast(*this); + } catch (std::underflow_error&) { + if constexpr (std::is_same_v) { + warn("requested value of integer is too small; returning INT_MIN"); + } else if constexpr (std::is_same_v) { + warn("unsigned integer value request for negative number; returning 0"); + } else if constexpr (std::is_same_v) { + warn("unsigned value request for negative number; returning 0"); + } else { + warn( + "underflow while converting integer object; returning smallest possible " + "value"); + } + return std::numeric_limits::min(); + } catch (std::overflow_error&) { + if constexpr (std::is_same_v) { + warn("requested value of integer is too big; returning INT_MAX"); + } else if constexpr (std::is_same_v) { + warn("requested value of unsigned integer is too big; returning UINT_MAX"); + } else { + warn( + "overflow while converting integer object; returning largest possible " + "value"); + } + return std::numeric_limits::max(); + } + } + // Return true if object value is equal to the 'rhs' value. Return false if the object is // not a valid Integer. friend bool @@ -367,7 +403,7 @@ namespace qpdf return std::cmp_greater(lhs.value(), rhs) ? std::strong_ordering::greater : std::strong_ordering::equal; } - }; + }; // class Integer bool operator==(std::integral auto lhs, Integer const& rhs) diff --git a/libtests/CMakeLists.txt b/libtests/CMakeLists.txt index db5dfa3..d388fe7 100644 --- a/libtests/CMakeLists.txt +++ b/libtests/CMakeLists.txt @@ -24,6 +24,7 @@ set(TEST_PROGRAMS md5 nntree numrange + objects obj_table pdf_version pl_function diff --git a/libtests/objects.cc b/libtests/objects.cc new file mode 100644 index 0000000..ee9c7c5 --- /dev/null +++ b/libtests/objects.cc @@ -0,0 +1,159 @@ +#include + +// This program tests miscellaneous object handle functionality + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static char const* whoami = nullptr; + +void +usage() +{ + std::cerr << "Usage: " << whoami << " n filename1 [arg2]" << '\n'; + exit(2); +} + +#define assert_compare_numbers(expected, expr) compare_numbers(#expr, expected, expr) + +template +static void +compare_numbers(char const* description, T1 const& expected, T2 const& actual) +{ + if (expected != actual) { + std::cerr << description << ": expected = " << expected << "; actual = " << actual << '\n'; + } +} + +static void +test_0(QPDF& pdf, char const* arg2) +{ + // Test int size checks. This test will fail if int and long long are the same size. + QPDFObjectHandle t = pdf.getTrailer(); + unsigned long long q1_l = 3ULL * QIntC::to_ulonglong(INT_MAX); + long long q1 = QIntC::to_longlong(q1_l); + long long q2_l = 3LL * QIntC::to_longlong(INT_MIN); + long long q2 = QIntC::to_longlong(q2_l); + unsigned int q3_i = UINT_MAX; + long long q3 = QIntC::to_longlong(q3_i); + t.replaceKey("/Q1", QPDFObjectHandle::newInteger(q1)); + t.replaceKey("/Q2", QPDFObjectHandle::newInteger(q2)); + t.replaceKey("/Q3", QPDFObjectHandle::newInteger(q3)); + assert_compare_numbers(q1, t.getKey("/Q1").getIntValue()); + assert_compare_numbers(q1_l, t.getKey("/Q1").getUIntValue()); + assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt()); + try { + assert_compare_numbers(0u, QPDFObjectHandle::newNull().getUIntValueAsUInt()); + std::cerr << "convert null to uint did not throw\n"; + } catch (QPDFExc const&) { + std::cerr << "caught expected type error\n"; + } + assert_compare_numbers(std::numeric_limits::max(), Integer(q1).value()); + assert_compare_numbers(std::numeric_limits::min(), Integer(-q1).value()); + try { + int8_t q1_8 = Integer(q1); + std::cerr << "q1_8: " << std::to_string(q1_8) << '\n'; + } catch (std::overflow_error const&) { + std::cerr << "caught expected int8_t overflow error\n"; + } + try { + int8_t q1_8 = Integer(-q1); + std::cerr << "q1_8: " << std::to_string(q1_8) << '\n'; + } catch (std::underflow_error const&) { + std::cerr << "caught expected int8_t underflow error\n"; + } + assert_compare_numbers(std::numeric_limits::max(), Integer(q1).value()); + assert_compare_numbers(0, Integer(-q1).value()); + try { + uint8_t q1_u8 = Integer(q1); + std::cerr << "q1_u8: " << std::to_string(q1_u8) << '\n'; + } catch (std::overflow_error const&) { + std::cerr << "caught expected uint8_t overflow error\n"; + } + try { + uint8_t q1_u8 = Integer(-q1); + std::cerr << "q1_u8: " << std::to_string(q1_u8) << '\n'; + } catch (std::underflow_error const&) { + std::cerr << "caught expected uint8_t underflow error\n"; + } + assert_compare_numbers(UINT_MAX, t.getKey("/Q1").getUIntValueAsUInt()); + assert_compare_numbers(q2_l, t.getKey("/Q2").getIntValue()); + assert_compare_numbers(0U, t.getKey("/Q2").getUIntValue()); + assert_compare_numbers(INT_MIN, t.getKey("/Q2").getIntValueAsInt()); + assert_compare_numbers(0U, t.getKey("/Q2").getUIntValueAsUInt()); + assert_compare_numbers(INT_MAX, t.getKey("/Q3").getIntValueAsInt()); + assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt()); +} + +void +runtest(int n, char const* filename1, char const* arg2) +{ + // Most tests here are crafted to work on specific files. Look at + // the test suite to see how the test is invoked to find the file + // that the test is supposed to operate on. + + std::set ignore_filename = {}; + + QPDF pdf; + std::shared_ptr file_buf; + FILE* filep = nullptr; + if (ignore_filename.contains(n)) { + // Ignore filename argument entirely + } else { + size_t size = 0; + QUtil::read_file_into_memory(filename1, file_buf, size); + pdf.processMemoryFile(filename1, file_buf.get(), size); + } + + std::map test_functions = { + {0, test_0}, + }; + + auto fn = test_functions.find(n); + if (fn == test_functions.end()) { + throw std::runtime_error(std::string("invalid test ") + QUtil::int_to_string(n)); + } + (fn->second)(pdf, arg2); + + if (filep) { + fclose(filep); + } + std::cout << "test " << n << " done" << '\n'; +} + +int +main(int argc, char* argv[]) +{ + QUtil::setLineBuf(stdout); + if ((whoami = strrchr(argv[0], '/')) == nullptr) { + whoami = argv[0]; + } else { + ++whoami; + } + + if ((argc < 3) || (argc > 4)) { + usage(); + } + + try { + int n = QUtil::string_to_int(argv[1]); + char const* filename1 = argv[2]; + char const* arg2 = argv[3]; + runtest(n, filename1, arg2); + } catch (std::exception& e) { + std::cerr << e.what() << '\n'; + exit(2); + } + + return 0; +} diff --git a/libtests/qtest/objects.test b/libtests/qtest/objects.test new file mode 100644 index 0000000..7dec720 --- /dev/null +++ b/libtests/qtest/objects.test @@ -0,0 +1,22 @@ +#!/usr/bin/env perl +require 5.008; +use warnings; +use strict; + + +chdir("objects") or die "chdir testdir failed: $!\n"; + +require TestDriver; + + +my $td = new TestDriver('objects'); + +my $n_tests = 1; + + +$td->runtest("integer type checks", + {$td->COMMAND => "objects 0 minimal.pdf"}, + {$td->FILE => "test0.out", $td->EXIT_STATUS => 0}, + $td->NORMALIZE_NEWLINES); + +$td->report($n_tests); diff --git a/libtests/qtest/objects/minimal.pdf b/libtests/qtest/objects/minimal.pdf new file mode 100644 index 0000000..a7e01f9 --- /dev/null +++ b/libtests/qtest/objects/minimal.pdf @@ -0,0 +1,79 @@ +%PDF-1.3 +1 0 obj +<< + /Type /Catalog + /Pages 2 0 R +>> +endobj + +2 0 obj +<< + /Type /Pages + /Kids [ + 3 0 R + ] + /Count 1 +>> +endobj + +3 0 obj +<< + /Type /Page + /Parent 2 0 R + /MediaBox [0 0 612 792] + /Contents 4 0 R + /Resources << + /ProcSet 5 0 R + /Font << + /F1 6 0 R + >> + >> +>> +endobj + +4 0 obj +<< + /Length 44 +>> +stream +BT + /F1 24 Tf + 72 720 Td + (Potato) Tj +ET +endstream +endobj + +5 0 obj +[ + /PDF + /Text +] +endobj + +6 0 obj +<< + /Type /Font + /Subtype /Type1 + /Name /F1 + /BaseFont /Helvetica + /Encoding /WinAnsiEncoding +>> +endobj + +xref +0 7 +0000000000 65535 f +0000000009 00000 n +0000000063 00000 n +0000000135 00000 n +0000000307 00000 n +0000000403 00000 n +0000000438 00000 n +trailer << + /Size 7 + /Root 1 0 R +>> +startxref +556 +%%EOF diff --git a/qpdf/qtest/qpdf/test62.out b/libtests/qtest/objects/test0.out index 50bdf99..94a1ddc 100644 --- a/qpdf/qtest/qpdf/test62.out +++ b/libtests/qtest/objects/test0.out @@ -1,7 +1,16 @@ requested value of integer is too big; returning INT_MAX +caught expected type error +overflow while converting integer object; returning largest possible value +underflow while converting integer object; returning smallest possible value +caught expected int8_t overflow error +caught expected int8_t underflow error +overflow while converting integer object; returning largest possible value +underflow while converting integer object; returning smallest possible value +caught expected uint8_t overflow error +caught expected uint8_t underflow error requested value of unsigned integer is too big; returning UINT_MAX unsigned value request for negative number; returning 0 requested value of integer is too small; returning INT_MIN unsigned integer value request for negative number; returning 0 requested value of integer is too big; returning INT_MAX -test 62 done +test 0 done diff --git a/qpdf/qtest/error-condition.test b/qpdf/qtest/error-condition.test index 8c2e523..0f31f67 100644 --- a/qpdf/qtest/error-condition.test +++ b/qpdf/qtest/error-condition.test @@ -109,17 +109,12 @@ $td->runtest("C API: no recovery", {$td->FILE => "c-no-recovery.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); - -$td->runtest("integer type checks", - {$td->COMMAND => "test_driver 62 minimal.pdf"}, - {$td->FILE => "test62.out", $td->EXIT_STATUS => 0}, - $td->NORMALIZE_NEWLINES); $td->runtest("getValueAs... accessor checks", {$td->COMMAND => "test_driver 85 -"}, {$td->FILE => "test85.out", $td->EXIT_STATUS => 0}, $td->NORMALIZE_NEWLINES); -$n_tests += @badfiles + 11; +$n_tests += @badfiles + 10; # Recovery tests. These are mostly after-the-fact -- when recovery # was implemented, some degree of recovery was possible on many of the diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc index b9a4e2f..834cbc4 100644 --- a/qpdf/test_driver.cc +++ b/qpdf/test_driver.cc @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -189,8 +190,7 @@ static void compare_numbers(char const* description, T1 const& expected, T2 const& actual) { if (expected != actual) { - std::cerr << description << ": expected = " << expected << "; actual = " << actual - << std::endl; + std::cerr << description << ": expected = " << expected << "; actual = " << actual << '\n'; } } @@ -2279,28 +2279,8 @@ test_61(QPDF& pdf, char const* arg2) static void test_62(QPDF& pdf, char const* arg2) { - // Test int size checks. This test will fail if int and long - // long are the same size. - QPDFObjectHandle t = pdf.getTrailer(); - unsigned long long q1_l = 3ULL * QIntC::to_ulonglong(INT_MAX); - long long q1 = QIntC::to_longlong(q1_l); - long long q2_l = 3LL * QIntC::to_longlong(INT_MIN); - long long q2 = QIntC::to_longlong(q2_l); - unsigned int q3_i = UINT_MAX; - long long q3 = QIntC::to_longlong(q3_i); - t.replaceKey("/Q1", QPDFObjectHandle::newInteger(q1)); - t.replaceKey("/Q2", QPDFObjectHandle::newInteger(q2)); - t.replaceKey("/Q3", QPDFObjectHandle::newInteger(q3)); - assert_compare_numbers(q1, t.getKey("/Q1").getIntValue()); - assert_compare_numbers(q1_l, t.getKey("/Q1").getUIntValue()); - assert_compare_numbers(INT_MAX, t.getKey("/Q1").getIntValueAsInt()); - assert_compare_numbers(UINT_MAX, t.getKey("/Q1").getUIntValueAsUInt()); - assert_compare_numbers(q2_l, t.getKey("/Q2").getIntValue()); - assert_compare_numbers(0U, t.getKey("/Q2").getUIntValue()); - assert_compare_numbers(INT_MIN, t.getKey("/Q2").getIntValueAsInt()); - assert_compare_numbers(0U, t.getKey("/Q2").getUIntValueAsUInt()); - assert_compare_numbers(INT_MAX, t.getKey("/Q3").getIntValueAsInt()); - assert_compare_numbers(UINT_MAX, t.getKey("/Q3").getUIntValueAsUInt()); + // Test int size checks. This test will fail if int and long long are the same size. + // Moved to libtests/objects } static void