diff --git a/include/qpdf/ObjectHandle.hh b/include/qpdf/ObjectHandle.hh index 094a1cf..14951d1 100644 --- a/include/qpdf/ObjectHandle.hh +++ b/include/qpdf/ObjectHandle.hh @@ -27,7 +27,6 @@ #include #include #include -#include #include #include @@ -42,6 +41,7 @@ namespace qpdf class Array; class BaseDictionary; class Dictionary; + class Integer; class Stream; enum typed : std::uint8_t { strict = 0, any_flag = 1, optional = 2, any = 3, error = 4 }; @@ -127,6 +127,7 @@ namespace qpdf inline void assign(qpdf_object_type_e required, BaseHandle&& other); std::string description() const; + std::invalid_argument invalid_error(std::string const& method) const; std::runtime_error type_error(char const* expected_type) const; QPDFExc type_error(char const* expected_type, std::string const& message) const; char const* type_name() const; diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 590257e..fe0ce8f 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -46,10 +46,15 @@ #include #include -namespace qpdf::is +namespace qpdf { - class OffsetBuffer; -} + class Dictionary; + + namespace is + { + class OffsetBuffer; + } +} // namespace qpdf class QPDF_Stream; class BitStream; @@ -1006,7 +1011,7 @@ class QPDF void checkLinearizationInternal(); void dumpLinearizationDataInternal(); void linearizationWarning(std::string_view); - QPDFObjectHandle readHintStream(Pipeline&, qpdf_offset_t offset, size_t length); + qpdf::Dictionary readHintStream(Pipeline&, qpdf_offset_t offset, size_t length); void readHPageOffset(BitStream); void readHSharedObject(BitStream); void readHGeneric(BitStream, HGeneric&); diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index af62923..3dd04da 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -806,16 +806,36 @@ QPDFObjectHandle::getValueAsBool(bool& value) const return false; } -// Integer accessors +// Integer methods + +Integer::Integer(long long value) : + BaseHandle(QPDFObject::create(value)) +{ +} + +QPDFObjectHandle +QPDFObjectHandle::newInteger(long long value) +{ + return {QPDFObject::create(value)}; +} + +int64_t +Integer::value() const +{ + auto* i = as(); + if (!i) { + throw invalid_error("Integer"); + } + return i->val; +} long long QPDFObjectHandle::getIntValue() const { - if (auto integer = as()) { - return integer->val; + if (auto const integer = Integer(*this)) { + return integer; } else { typeWarning("integer", "returning 0"); - QTC::TC("qpdf", "QPDFObjectHandle integer returning 0"); return 0; } } @@ -823,8 +843,8 @@ QPDFObjectHandle::getIntValue() const bool QPDFObjectHandle::getValueAsInt(long long& value) const { - if (auto integer = as()) { - value = integer->val; + if (auto const integer = Integer(*this)) { + value = integer; return true; } return false; @@ -833,16 +853,18 @@ QPDFObjectHandle::getValueAsInt(long long& value) const int QPDFObjectHandle::getIntValueAsInt() const { - long long v = getIntValue(); - if (v < INT_MIN) { + try { + return Integer(*this); + } catch (std::underflow_error&) { warn("requested value of integer is too small; returning INT_MIN"); return INT_MIN; - } - if (v > INT_MAX) { + } catch (std::overflow_error&) { warn("requested value of integer is too big; returning INT_MAX"); return INT_MAX; + } catch (std::invalid_argument&) { + typeWarning("integer", "returning 0"); + return 0; } - return static_cast(v); } bool @@ -858,12 +880,14 @@ QPDFObjectHandle::getValueAsInt(int& value) const unsigned long long QPDFObjectHandle::getUIntValue() const { - long long v = getIntValue(); - if (v < 0) { + try { + return Integer(*this); + } catch (std::underflow_error&) { warn("unsigned value request for negative number; returning 0"); return 0; - } else { - return static_cast(v); + } catch (std::invalid_argument&) { + typeWarning("integer", "returning 0"); + return 0; } } @@ -880,16 +904,18 @@ QPDFObjectHandle::getValueAsUInt(unsigned long long& value) const unsigned int QPDFObjectHandle::getUIntValueAsUInt() const { - long long v = getIntValue(); - if (v < 0) { + try { + return Integer(*this); + } catch (std::underflow_error&) { warn("unsigned integer value request for negative number; returning 0"); return 0; - } - if (v > UINT_MAX) { + } catch (std::overflow_error&) { warn("requested value of unsigned integer is too big; returning UINT_MAX"); return UINT_MAX; + } catch (std::invalid_argument&) { + typeWarning("integer", "returning 0"); + return 0; } - return static_cast(v); } bool @@ -1329,7 +1355,7 @@ QPDFObjectHandle::rotatePage(int angle, bool relative) } new_angle = (new_angle + 360) % 360; // Make this explicit even with new_angle == 0 since /Rotate can be inherited. - replaceKey("/Rotate", QPDFObjectHandle::newInteger(new_angle)); + replaceKey("/Rotate", Integer(new_angle)); } void @@ -1595,12 +1621,11 @@ QPDFObjectHandle::parseContentStream_data( QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image"); warn( context, - QPDFExc( - qpdf_e_damaged_pdf, - description, - "stream data", - input.tell(), - "EOF found while reading inline image")); + {qpdf_e_damaged_pdf, + description, + "stream data", + input.tell(), + "EOF found while reading inline image"}); } else { QTC::TC("qpdf", "QPDFObjectHandle inline image token"); if (callbacks) { @@ -1658,12 +1683,6 @@ QPDFObjectHandle::newNull() } QPDFObjectHandle -QPDFObjectHandle::newInteger(long long value) -{ - return {QPDFObject::create(value)}; -} - -QPDFObjectHandle QPDFObjectHandle::newReal(std::string const& value) { return {QPDFObject::create(value)}; @@ -1932,6 +1951,11 @@ QPDFObjectHandle::assertInitialized() const } } +std::invalid_argument +BaseHandle::invalid_error(std::string const& method) const +{ + return std::invalid_argument(method + " operation attempted on invalid object"); +} std::runtime_error BaseHandle::type_error(char const* expected_type) const { @@ -1976,9 +2000,7 @@ void QPDFObjectHandle::assertType(char const* type_name, bool istype) const { if (!istype) { - throw std::runtime_error( - std::string("operation for ") + type_name + " attempted on object of type " + - QPDFObjectHandle(*this).getTypeName()); + throw type_error(type_name); } } diff --git a/libqpdf/QPDF_linearization.cc b/libqpdf/QPDF_linearization.cc index bcce0e6..f66b336 100644 --- a/libqpdf/QPDF_linearization.cc +++ b/libqpdf/QPDF_linearization.cc @@ -122,27 +122,18 @@ QPDF::isLinearized() continue; } - auto candidate = getObject(toI(QUtil::string_to_ll(t1.getValue().data())), 0); - if (!candidate.isDictionary()) { - return false; - } - - auto linkey = candidate.getKey("/Linearized"); + Dictionary candidate = getObject(toI(QUtil::string_to_ll(t1.getValue().data())), 0); + auto linkey = candidate["/Linearized"]; if (!(linkey.isNumber() && toI(floor(linkey.getNumericValue())) == 1)) { return false; } - auto L = candidate.getKey("/L"); - if (!L.isInteger()) { - return false; - } - qpdf_offset_t Li = L.getIntValue(); m->file->seek(0, SEEK_END); - if (Li != m->file->tell()) { - QTC::TC("qpdf", "QPDF /L mismatch"); + Integer L = candidate["/L"]; + if (L != m->file->tell()) { return false; } - m->linp.file_size = Li; + m->linp.file_size = L; m->lindict = candidate; return true; } @@ -159,80 +150,57 @@ QPDF::readLinearizationData() } // /L is read and stored in linp by isLinearized() - QPDFObjectHandle H = m->lindict.getKey("/H"); - QPDFObjectHandle O = m->lindict.getKey("/O"); - QPDFObjectHandle E = m->lindict.getKey("/E"); - QPDFObjectHandle N = m->lindict.getKey("/N"); - QPDFObjectHandle T = m->lindict.getKey("/T"); - QPDFObjectHandle P = m->lindict.getKey("/P"); - - if (!(H.isArray() && O.isInteger() && E.isInteger() && N.isInteger() && T.isInteger() && - (P.isInteger() || P.null()))) { + Array H = m->lindict["/H"]; // hint table offset/length for primary and overflow hint tables + auto H_size = H.size(); + Integer H_0 = H[0]; // hint table offset + Integer H_1 = H[1]; // hint table length + Integer H_2 = H[2]; // hint table offset for overflow hint table + Integer H_3 = H[3]; // hint table length for overflow hint table + Integer O = m->lindict["/O"]; + Integer E = m->lindict["/E"]; + Integer N = m->lindict["/N"]; + Integer T = m->lindict["/T"]; + auto P_oh = m->lindict["/P"]; + Integer P = P_oh; // first page number + QTC::TC("qpdf", "QPDF P absent in lindict", P ? 0 : 1); + + if (!(H && O && E && N && T && (P || P_oh.null()))) { throw damagedPDF( "linearization dictionary", "some keys in linearization dictionary are of the wrong type"); } - // Hint table array: offset length [ offset length ] - size_t n_H_items = H.size(); - if (!(n_H_items == 2 || n_H_items == 4)) { + if (!(H_size == 2 || H_size == 4)) { throw damagedPDF("linearization dictionary", "H has the wrong number of items"); } - std::vector H_items; - for (auto const& oh: H.as_array()) { - if (oh.isInteger()) { - H_items.push_back(oh.getIntValueAsInt()); - } else { - throw damagedPDF("linearization dictionary", "some H items are of the wrong type"); - } - } - - // H: hint table offset/length for primary and overflow hint tables - int H0_offset = H_items.at(0); - int H0_length = H_items.at(1); - int H1_offset = 0; - int H1_length = 0; - if (H_items.size() == 4) { - // Acrobat doesn't read or write these (as PDF 1.4), so we don't have a way to generate a - // test case. - // QTC::TC("qpdf", "QPDF overflow hint table"); - H1_offset = H_items.at(2); - H1_length = H_items.at(3); - } - - // P: first page number - int first_page = 0; - if (P.isInteger()) { - QTC::TC("qpdf", "QPDF P present in lindict"); - first_page = P.getIntValueAsInt(); - } else { - QTC::TC("qpdf", "QPDF P absent in lindict"); + if (!(H_0 && H_1 && (H_size == 2 || (H_2 && H_3)))) { + throw damagedPDF("linearization dictionary", "some H items are of the wrong type"); } // Store linearization parameter data // Various places in the code use linp.npages, which is initialized from N, to pre-allocate // memory, so make sure it's accurate and bail right now if it's not. - if (N.getIntValue() != static_cast(getAllPages().size())) { + if (N != getAllPages().size()) { throw damagedPDF("linearization hint table", "/N does not match number of pages"); } // file_size initialized by isLinearized() - m->linp.first_page_object = O.getIntValueAsInt(); - m->linp.first_page_end = E.getIntValue(); - m->linp.npages = N.getUIntValueAsUInt(); - m->linp.xref_zero_offset = T.getIntValue(); - m->linp.first_page = first_page; - m->linp.H_offset = H0_offset; - m->linp.H_length = H0_length; + m->linp.first_page_object = O; + m->linp.first_page_end = E; + m->linp.npages = N; + m->linp.xref_zero_offset = T; + m->linp.first_page = P ? P : 0; + m->linp.H_offset = H_0; + m->linp.H_length = H_1; // Read hint streams Pl_Buffer pb("hint buffer"); - QPDFObjectHandle H0 = readHintStream(pb, H0_offset, toS(H0_length)); - if (H1_offset) { - (void)readHintStream(pb, H1_offset, toS(H1_length)); + auto H0 = readHintStream(pb, H_0, H_1); + if (H_2) { + (void)readHintStream(pb, H_2, H_3); } // PDF 1.4 hint tables that we ignore: @@ -246,8 +214,8 @@ QPDF::readLinearizationData() // /L page label // Individual hint table offsets - QPDFObjectHandle HS = H0.getKey("/S"); // shared object - QPDFObjectHandle HO = H0.getKey("/O"); // outline + Integer HS = H0["/S"]; // shared object + Integer HO = H0["/O"]; // outline auto hbp = pb.getBufferSharedPointer(); Buffer* hb = hbp.get(); @@ -256,22 +224,22 @@ QPDF::readLinearizationData() readHPageOffset(BitStream(h_buf, h_size)); - int HSi = HS.getIntValueAsInt(); - if ((HSi < 0) || (toS(HSi) >= h_size)) { + size_t HSi = HS; + if (HSi < 0 || HSi >= h_size) { throw damagedPDF("linearization hint table", "/S (shared object) offset is out of bounds"); } - readHSharedObject(BitStream(h_buf + HSi, h_size - toS(HSi))); + readHSharedObject(BitStream(h_buf + HSi, h_size - HSi)); - if (HO.isInteger()) { - int HOi = HO.getIntValueAsInt(); - if ((HOi < 0) || (toS(HOi) >= h_size)) { + if (HO) { + if (HO < 0 || HO >= h_size) { throw damagedPDF("linearization hint table", "/O (outline) offset is out of bounds"); } - readHGeneric(BitStream(h_buf + HOi, h_size - toS(HOi)), m->outline_hints); + size_t HOi = HO; + readHGeneric(BitStream(h_buf + HO, h_size - HOi), m->outline_hints); } } -QPDFObjectHandle +Dictionary QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) { auto H = readObjectAtOffset(offset, "linearization hint stream", false); @@ -282,18 +250,14 @@ QPDF::readHintStream(Pipeline& pl, qpdf_offset_t offset, size_t length) throw damagedPDF("linearization dictionary", "hint table is not a stream"); } - QPDFObjectHandle Hdict = H.getDict(); + Dictionary Hdict = H.getDict(); // Some versions of Acrobat make /Length indirect and place it immediately after the stream, // increasing length to cover it, even though the specification says all objects in the // linearization parameter dictionary must be direct. We have to get the file position of the // end of length in this case. - QPDFObjectHandle length_obj = Hdict.getKey("/Length"); - if (length_obj.isIndirect()) { - QTC::TC("qpdf", "QPDF hint table length indirect"); - // Force resolution - (void)length_obj.getIntValue(); - ObjCache& oc2 = m->obj_cache[length_obj.getObjGen()]; + if (Hdict["/Length"].indirect()) { + ObjCache& oc2 = m->obj_cache[Hdict["/Length"]]; min_end_offset = oc2.end_before_space; max_end_offset = oc2.end_after_space; } else { diff --git a/libqpdf/qpdf/QPDFObjectHandle_private.hh b/libqpdf/qpdf/QPDFObjectHandle_private.hh index cd71327..c213f1b 100644 --- a/libqpdf/qpdf/QPDFObjectHandle_private.hh +++ b/libqpdf/qpdf/QPDFObjectHandle_private.hh @@ -7,6 +7,11 @@ #include #include +#include +#include + +using namespace std::literals; + namespace qpdf { class Array final: public BaseHandle @@ -285,6 +290,97 @@ namespace qpdf ~Dictionary() = default; }; + class Integer final: public BaseHandle + { + public: + Integer() = default; + Integer(Integer const&) = default; + Integer(Integer&&) = default; + Integer& operator=(Integer const&) = default; + Integer& operator=(Integer&&) = default; + ~Integer() = default; + + explicit Integer(long long value); + + explicit Integer(std::integral auto value) : + Integer(static_cast(value)) + { + if constexpr ( + std::numeric_limits::max() > + std::numeric_limits::max()) { + if (value > std::numeric_limits::max()) { + throw std::overflow_error("overflow constructing Integer"); + } + } + } + + Integer(QPDFObjectHandle const& oh) : + BaseHandle(oh.type_code() == ::ot_integer ? oh : QPDFObjectHandle()) + { + } + + Integer(QPDFObjectHandle&& oh) : + BaseHandle(oh.type_code() == ::ot_integer ? std::move(oh) : QPDFObjectHandle()) + { + } + + // 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, + // throw a std::overflow_error or std::underflow_error exception. + template + operator T() const + { + auto v = value(); + + if (std::cmp_greater(v, std::numeric_limits::max())) { + throw std::overflow_error("Integer conversion overflow"); + } + if (std::cmp_less(v, std::numeric_limits::min())) { + throw std::underflow_error("Integer conversion underflow"); + } + return static_cast(v); + } + + // Return the integer value. If the object is not a valid integer, throw a + // std::invalid_argument exception. + int64_t value() const; + + // Return true if object value is equal to the 'rhs' value. Return false if the object is + // not a valid Integer. + friend bool + operator==(Integer const& lhs, std::integral auto rhs) + { + return lhs && std::cmp_equal(lhs.value(), rhs); + } + + // Compare the object value to the 'rhs' value. Throw a std::invalid_argument exception if + // the object is not a valid Integer. + friend std::strong_ordering + operator<=>(Integer const& lhs, std::integral auto rhs) + { + if (!lhs) { + throw lhs.invalid_error("Integer"); + } + if (std::cmp_less(lhs.value(), rhs)) { + return std::strong_ordering::less; + } + return std::cmp_greater(lhs.value(), rhs) ? std::strong_ordering::greater + : std::strong_ordering::equal; + } + }; + + bool + operator==(std::integral auto lhs, Integer const& rhs) + { + return rhs == lhs; + } + + std::strong_ordering + operator<=>(std::integral auto lhs, Integer const& rhs) + { + return rhs <=> lhs; + } + class Name final: public BaseHandle { public: diff --git a/libqpdf/qpdf/QPDFObject_private.hh b/libqpdf/qpdf/QPDFObject_private.hh index 0637069..9dd4b92 100644 --- a/libqpdf/qpdf/QPDFObject_private.hh +++ b/libqpdf/qpdf/QPDFObject_private.hh @@ -27,6 +27,7 @@ namespace qpdf class Array; class BaseDictionary; class Dictionary; + class Integer; class Stream; } // namespace qpdf @@ -123,6 +124,7 @@ class QPDF_Integer final { friend class QPDFObject; friend class qpdf::BaseHandle; + friend class qpdf::Integer; friend class QPDFObjectHandle; QPDF_Integer(long long val) : diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 2fee084..6a14817 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -7,12 +7,9 @@ QPDF check obj 1 QPDF object stream offsets not increasing 0 QPDF ignore self-referential object stream 0 QPDF object stream contains id < 1 0 -QPDF hint table length indirect 0 QPDF hint table length direct 0 -QPDF P absent in lindict 0 -QPDF P present in lindict 0 +QPDF P absent in lindict 1 QPDF expected n n obj 0 -QPDF /L mismatch 0 QPDF err /T mismatch 0 QPDF err /O mismatch 0 QPDF opt direct pages resource 1 @@ -277,7 +274,6 @@ QPDFParser bad token in parseRemainder 0 QPDFParser eof in parse 0 QPDFParser eof in parseRemainder 0 QPDFObjectHandle boolean returning false 0 -QPDFObjectHandle integer returning 0 0 QPDFObjectHandle real returning 0.0 0 QPDFObjectHandle name returning dummy name 0 QPDFObjectHandle string returning empty string 0