diff --git a/include/qpdf/ObjectHandle.hh b/include/qpdf/ObjectHandle.hh index 42450d9..cacc14e 100644 --- a/include/qpdf/ObjectHandle.hh +++ b/include/qpdf/ObjectHandle.hh @@ -22,9 +22,10 @@ #include #include +#include #include -#include +#include class QPDF_Dictionary; class QPDFObject; @@ -32,6 +33,11 @@ class QPDFObjectHandle; namespace qpdf { + class Dictionary; + class BaseDictionary; + + enum typed : std::uint8_t { strict = 0, any_flag = 1, optional = 2, any = 3, error = 4}; + // Basehandle is only used as a base-class for QPDFObjectHandle like classes. Currently the only // methods exposed in public API are operators to convert derived objects to QPDFObjectHandle, // QPDFObjGen and bool. @@ -44,6 +50,8 @@ namespace qpdf // The rest of the header file is for qpdf internal use only. + inline qpdf_object_type_e type_code() const; + protected: BaseHandle() = default; BaseHandle(std::shared_ptr const& obj) : diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh index 5e8a2a6..ea9ac6a 100644 --- a/include/qpdf/QPDFObjectHandle.hh +++ b/include/qpdf/QPDFObjectHandle.hh @@ -304,7 +304,7 @@ class QPDFObjectHandle final: public qpdf::BaseHandle // other one changes color." This does not perform a structural comparison of the contents of // the objects. QPDF_DLL - bool isSameObjectAs(QPDFObjectHandle const&) const noexcept; + bool isSameObjectAs(QPDFObjectHandle const&) const; // Return type code and type name of underlying object. These are useful for doing rapid type // tests (like switch statements) or for testing and debugging. @@ -1355,6 +1355,8 @@ class QPDFObjectHandle final: public qpdf::BaseHandle void writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect = false) const; + inline qpdf::Dictionary as_dictionary(qpdf::typed options = qpdf::typed::any) const; + private: QPDF_Array* asArray() const; QPDF_Bool* asBool() const; diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc index 30f854b..961c3c2 100644 --- a/libqpdf/QPDFObjectHandle.cc +++ b/libqpdf/QPDFObjectHandle.cc @@ -1,4 +1,4 @@ -#include +#include #include #include @@ -38,7 +38,6 @@ #include using namespace std::literals; - using namespace qpdf; BaseHandle:: @@ -230,7 +229,7 @@ LastChar::getLastChar() } bool -QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const noexcept +QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const { return this->obj == rhs.obj; } @@ -986,9 +985,9 @@ QPDFObjectHandle::ditems() bool QPDFObjectHandle::hasKey(std::string const& key) const { - auto dict = asDictionary(); + auto dict = as_dictionary(strict); if (dict) { - return dict->hasKey(key); + return dict.hasKey(key); } else { typeWarning("dictionary", "returning false for a key containment request"); QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey"); @@ -999,14 +998,13 @@ QPDFObjectHandle::hasKey(std::string const& key) const QPDFObjectHandle QPDFObjectHandle::getKey(std::string const& key) const { - if (auto dict = asDictionary()) { - return dict->getKey(key); - } else { - typeWarning("dictionary", "returning null for attempted key retrieval"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey"); - static auto constexpr msg = " -> null returned from getting key $VD from non-Dictionary"sv; - return QPDF_Null::create(obj, msg, ""); + if (auto dict = as_dictionary(strict)) { + return dict.getKey(key); } + typeWarning("dictionary", "returning null for attempted key retrieval"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey"); + static auto constexpr msg = " -> null returned from getting key $VD from non-Dictionary"sv; + return QPDF_Null::create(obj, msg, ""); } QPDFObjectHandle @@ -1018,29 +1016,23 @@ QPDFObjectHandle::getKeyIfDict(std::string const& key) const std::set QPDFObjectHandle::getKeys() const { - std::set result; - auto dict = asDictionary(); - if (dict) { - result = dict->getKeys(); - } else { - typeWarning("dictionary", "treating as empty"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys"); + if (auto dict = as_dictionary(strict)) { + return dict.getKeys(); } - return result; + typeWarning("dictionary", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys"); + return {}; } std::map QPDFObjectHandle::getDictAsMap() const { - std::map result; - auto dict = asDictionary(); - if (dict) { - result = dict->getAsMap(); - } else { - typeWarning("dictionary", "treating as empty"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary empty map for asMap"); + if (auto dict = as_dictionary(strict)) { + return dict.getAsMap(); } - return result; + typeWarning("dictionary", "treating as empty"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary empty map for asMap"); + return {}; } // Array and Name accessors @@ -1221,14 +1213,13 @@ QPDFObjectHandle::getUniqueResourceName( void QPDFObjectHandle::replaceKey(std::string const& key, QPDFObjectHandle const& value) { - auto dict = asDictionary(); - if (dict) { + if (auto dict = as_dictionary(strict)) { checkOwnership(value); - dict->replaceKey(key, value); - } else { - typeWarning("dictionary", "ignoring key replacement request"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey"); + dict.replaceKey(key, value); + return; } + typeWarning("dictionary", "ignoring key replacement request"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey"); } QPDFObjectHandle @@ -1249,22 +1240,20 @@ QPDFObjectHandle::replaceKeyAndGetOld(std::string const& key, QPDFObjectHandle c void QPDFObjectHandle::removeKey(std::string const& key) { - auto dict = asDictionary(); - if (dict) { - dict->removeKey(key); - } else { - typeWarning("dictionary", "ignoring key removal request"); - QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey"); + if (auto dict = as_dictionary(strict)) { + dict.removeKey(key); + return; } + typeWarning("dictionary", "ignoring key removal request"); + QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey"); } QPDFObjectHandle QPDFObjectHandle::removeKeyAndGetOld(std::string const& key) { auto result = QPDFObjectHandle::newNull(); - auto dict = asDictionary(); - if (dict) { - result = dict->getKey(key); + if (auto dict = as_dictionary(strict)) { + result = dict.getKey(key); } removeKey(key); return result; @@ -2115,9 +2104,9 @@ QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams) this->obj = QPDF_Array::create(items); } else if (isDictionary()) { std::map items; - auto dict = asDictionary(); + auto dict = as_dictionary(strict); for (auto const& key: getKeys()) { - items[key] = dict->getKey(key); + items[key] = dict.getKey(key); items[key].makeDirect(visited, stop_at_streams); } this->obj = QPDF_Dictionary::create(items); diff --git a/libqpdf/QPDF_Dictionary.cc b/libqpdf/QPDF_Dictionary.cc index 2cd6f61..978e572 100644 --- a/libqpdf/QPDF_Dictionary.cc +++ b/libqpdf/QPDF_Dictionary.cc @@ -1,10 +1,10 @@ #include #include -#include +#include #include #include -#include +#include #include using namespace std::literals; @@ -96,31 +96,45 @@ QPDF_Dictionary::writeJSON(int json_version, JSON::Writer& p) p.writeEnd('}'); } +QPDF_Dictionary* +BaseDictionary::dict() const +{ + if (obj) { + if (auto d = obj->as()) { + return d; + } + } + throw std::runtime_error("Expected a dictionary but found a non-dictionary object"); + return nullptr; // unreachable +} + bool -QPDF_Dictionary::hasKey(std::string const& key) +BaseDictionary::hasKey(std::string const& key) const { - return ((this->items.count(key) > 0) && (!this->items[key].isNull())); + auto d = dict(); + return d->items.count(key) > 0 && !d->items[key].isNull(); } QPDFObjectHandle -QPDF_Dictionary::getKey(std::string const& key) +BaseDictionary::getKey(std::string const& key) const { + auto d = dict(); + // PDF spec says fetching a non-existent key from a dictionary returns the null object. - auto item = this->items.find(key); - if (item != this->items.end()) { + auto item = d->items.find(key); + if (item != d->items.end()) { // May be a null object return item->second; - } else { - static auto constexpr msg = " -> dictionary key $VD"sv; - return QPDF_Null::create(shared_from_this(), msg, key); } + static auto constexpr msg = " -> dictionary key $VD"sv; + return QPDF_Null::create(obj, msg, key); } std::set -QPDF_Dictionary::getKeys() +BaseDictionary::getKeys() { std::set result; - for (auto& iter: this->items) { + for (auto& iter: dict()->items) { if (!iter.second.isNull()) { result.insert(iter.first); } @@ -129,28 +143,29 @@ QPDF_Dictionary::getKeys() } std::map const& -QPDF_Dictionary::getAsMap() const +BaseDictionary::getAsMap() const { - return this->items; + return dict()->items; } void -QPDF_Dictionary::replaceKey(std::string const& key, QPDFObjectHandle value) +BaseDictionary::removeKey(std::string const& key) { - if (value.isNull() && !value.isIndirect()) { - // The PDF spec doesn't distinguish between keys with null values and missing keys. Allow - // indirect nulls which are equivalent to a dangling reference, which is permitted by the - // spec. - removeKey(key); - } else { - // add or replace value - this->items[key] = value; - } + // no-op if key does not exist + dict()->items.erase(key); } void -QPDF_Dictionary::removeKey(std::string const& key) +BaseDictionary::replaceKey(std::string const& key, QPDFObjectHandle value) { - // no-op if key does not exist - this->items.erase(key); + auto d = dict(); + if (value.isNull() && !value.isIndirect()) { + // The PDF spec doesn't distinguish between keys with null values and missing keys. + // Allow indirect nulls which are equivalent to a dangling reference, which is + // permitted by the spec. + d->items.erase(key); + } else { + // add or replace value + d->items[key] = value; + } } diff --git a/libqpdf/qpdf/QPDFObjectHandle_private.hh b/libqpdf/qpdf/QPDFObjectHandle_private.hh new file mode 100644 index 0000000..23d9f79 --- /dev/null +++ b/libqpdf/qpdf/QPDFObjectHandle_private.hh @@ -0,0 +1,74 @@ +#ifndef OBJECTHANDLE_PRIVATE_HH +#define OBJECTHANDLE_PRIVATE_HH + +#include + +#include +#include + +namespace qpdf +{ + // BaseDictionary is only used as a base class. It does not contain any methods exposed in the + // public API. + class BaseDictionary: public BaseHandle + { + public: + // The following methods are not part of the public API. + bool hasKey(std::string const& key) const; + QPDFObjectHandle getKey(std::string const& key) const; + std::set getKeys(); + std::map const& getAsMap() const; + void removeKey(std::string const& key); + void replaceKey(std::string const& key, QPDFObjectHandle value); + + protected: + BaseDictionary() = default; + BaseDictionary(std::shared_ptr const& obj) : + BaseHandle(obj) {}; + BaseDictionary(std::shared_ptr&& obj) : + BaseHandle(std::move(obj)) {}; + BaseDictionary(BaseDictionary const&) = default; + BaseDictionary& operator=(BaseDictionary const&) = default; + BaseDictionary(BaseDictionary&&) = default; + BaseDictionary& operator=(BaseDictionary&&) = default; + ~BaseDictionary() = default; + + QPDF_Dictionary* dict() const; + }; + + class Dictionary final: public BaseDictionary + { + public: + explicit Dictionary(std::shared_ptr const& obj) : + BaseDictionary(obj) + { + } + + explicit Dictionary(std::shared_ptr&& obj) : + BaseDictionary(std::move(obj)) + { + } + }; + + inline qpdf_object_type_e + BaseHandle::type_code() const + { + return obj ? obj->getResolvedTypeCode() : ::ot_uninitialized; + } + +} // namespace qpdf + +inline qpdf::Dictionary +QPDFObjectHandle::as_dictionary(qpdf::typed options) const +{ + if (options & qpdf::any_flag || type_code() == ::ot_dictionary || + (options & qpdf::optional && type_code() == ::ot_null)) { + return qpdf::Dictionary(obj); + } + if (options & qpdf::error) { + assertType("dictionary", false); + } + return qpdf::Dictionary(std::shared_ptr()); +} + +#endif // OBJECTHANDLE_PRIVATE_HH diff --git a/libqpdf/qpdf/QPDF_Dictionary.hh b/libqpdf/qpdf/QPDF_Dictionary.hh index 8713a45..ffa4d19 100644 --- a/libqpdf/qpdf/QPDF_Dictionary.hh +++ b/libqpdf/qpdf/QPDF_Dictionary.hh @@ -6,7 +6,10 @@ #include #include +#include #include +#include +#include class QPDF_Dictionary: public QPDFValue { @@ -19,22 +22,12 @@ class QPDF_Dictionary: public QPDFValue void writeJSON(int json_version, JSON::Writer& p) override; void disconnect() override; - // hasKey() and getKeys() treat keys with null values as if they aren't there. getKey() returns - // null for the value of a non-existent key. This is as per the PDF spec. - bool hasKey(std::string const&); - QPDFObjectHandle getKey(std::string const&); - std::set getKeys(); - std::map const& getAsMap() const; - - // If value is null, remove key; otherwise, replace the value of key, adding it if it does not - // exist. - void replaceKey(std::string const& key, QPDFObjectHandle value); - // Remove key, doing nothing if key does not exist - void removeKey(std::string const& key); - private: + friend class qpdf::BaseDictionary; + QPDF_Dictionary(std::map const& items); QPDF_Dictionary(std::map&& items); + std::map items; };