#include // This program tests miscellaneous object handle functionality #include #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()); } 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 { [[maybe_unused]] int8_t q1_8 = Integer(q1); } catch (std::overflow_error const&) { std::cerr << "caught expected int8_t overflow error\n"; } try { [[maybe_unused]] int8_t q1_8 = Integer(-q1); } 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 { [[maybe_unused]] uint8_t q1_u8 = Integer(q1); } catch (std::overflow_error const&) { std::cerr << "caught expected uint8_t overflow error\n"; } try { [[maybe_unused]] uint8_t q1_u8 = Integer(-q1); } 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()); } static void test_1(QPDF& pdf, char const* arg2) { // Test new dictionary methods. using namespace qpdf; auto d = Dictionary({{"/A", {}}, {"/B", Null()}, {"/C", Dictionary::empty()}}); // contains assert(!d.contains("/A")); assert(!d.contains("/B")); assert(d.contains("/C")); auto i = Integer(42); assert(!i.contains("/A")); // at assert(!d.at("/A")); assert(d.at("/B")); assert(d.at("/B").null()); assert(d.at("/C")); assert(!d.at("/C").null()); d.at("/C") = Integer(42); assert(d.at("/C") == 42); assert(!d.at("/D")); assert(d.at("/D").null()); assert(QPDFObjectHandle(d).getDictAsMap().contains("/D")); assert(QPDFObjectHandle(d).getDictAsMap().size() == 4); bool thrown = false; try { i.at("/A"); } catch (std::runtime_error const&) { thrown = true; } assert(thrown); // find assert(!d.find("/A")); assert(d.find("/B")); assert(d.find("/B").null()); assert(d.find("/C")); assert(Integer(d.find("/C")) == 42); d.find("/C") = Name("/DontPanic"); assert(Name(d.find("/C")) == "/DontPanic"); assert(!d.find("/E")); assert(!QPDFObjectHandle(d).getDictAsMap().contains("/E")); assert(QPDFObjectHandle(d).getDictAsMap().size() == 4); // replace assert(!i.replace("/A", Name("/DontPanic"))); Dictionary di = i.oh(); thrown = false; try { di.replace("/A", Name("/DontPanic")); } catch (std::runtime_error const&) { thrown = true; } assert(thrown); d.replace("/C", Integer(42)); assert(Integer(d["/C"]) == 42); assert(QPDFObjectHandle(d).getDictAsMap().size() == 4); } static void test_2(QPDF& pdf, char const* arg2) { // Test global limits. using namespace qpdf::global::options; using namespace qpdf::global::limits; // Check default values assert(parser_max_nesting() == 499); assert(parser_max_errors() == 15); assert(parser_max_container_size() == std::numeric_limits::max()); assert(parser_max_container_size_damaged() == 5'000); assert(default_limits()); // Test disabling optional default limits default_limits(false); assert(parser_max_nesting() == 499); assert(parser_max_errors() == 0); assert(parser_max_container_size() == std::numeric_limits::max()); assert(parser_max_container_size_damaged() == std::numeric_limits::max()); assert(!default_limits()); // Check disabling default limits is irreversible default_limits(true); assert(!default_limits()); // Test setting limits parser_max_nesting(11); parser_max_errors(12); parser_max_container_size(13); parser_max_container_size_damaged(14); assert(parser_max_nesting() == 11); assert(parser_max_errors() == 12); assert(parser_max_container_size() == 13); assert(parser_max_container_size_damaged() == 14); // Check disabling default limits does not override explicit limits default_limits(false); assert(parser_max_nesting() == 11); assert(parser_max_errors() == 12); assert(parser_max_container_size() == 13); assert(parser_max_container_size_damaged() == 14); // Test parameter checking QUtil::handle_result_code(qpdf_r_ok, ""); bool thrown = false; try { qpdf::global::handle_result(qpdf_r_success_mask); } catch (std::logic_error const&) { thrown = true; } assert(thrown); thrown = false; try { qpdf::global::get_uint32(qpdf_param_e(42)); } catch (std::logic_error const&) { thrown = true; } assert(thrown); thrown = false; try { qpdf::global::set_uint32(qpdf_param_e(42), 42); } catch (std::logic_error const&) { thrown = true; } assert(thrown); /* Test limit errors */ assert(qpdf::global::limit_errors() == 0); QPDFObjectHandle::parse("[[[[]]]]"); assert(qpdf::global::limit_errors() == 0); parser_max_nesting(3); try { QPDFObjectHandle::parse("[[[[[]]]]]"); } catch (std::exception&) { } assert(qpdf::global::limit_errors() == 1); try { QPDFObjectHandle::parse("[[[[[]]]]]"); } catch (std::exception&) { } assert(qpdf::global::limit_errors() == 2); } 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 = {1, 2}; 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}, {1, test_1}, {2, test_2}}; 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; }