From 37105710ee0b332a3020d4b3220c95b8f4267555 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Wed, 19 Jan 2022 09:31:28 -0500 Subject: Implement JSONHandler for recursively processing JSON --- libqpdf/JSON.cc | 78 ++++++++++++++++++++++++ libqpdf/JSONHandler.cc | 160 +++++++++++++++++++++++++++++++++++++++++++++++++ libqpdf/build.mk | 1 + 3 files changed, 239 insertions(+) create mode 100644 libqpdf/JSONHandler.cc (limited to 'libqpdf') diff --git a/libqpdf/JSON.cc b/libqpdf/JSON.cc index 423c0b0a..af98553e 100644 --- a/libqpdf/JSON.cc +++ b/libqpdf/JSON.cc @@ -90,6 +90,7 @@ std::string JSON::JSON_array::unparse(size_t depth) const } JSON::JSON_string::JSON_string(std::string const& utf8) : + utf8(utf8), encoded(encode_string(utf8)) { } @@ -311,6 +312,83 @@ JSON::isDictionary() const this->m->value.getPointer()); } +bool +JSON::getString(std::string& utf8) const +{ + auto v = dynamic_cast(this->m->value.getPointer()); + if (v == nullptr) + { + return false; + } + utf8 = v->utf8; + return true; +} + +bool +JSON::getNumber(std::string& value) const +{ + auto v = dynamic_cast(this->m->value.getPointer()); + if (v == nullptr) + { + return false; + } + value = v->encoded; + return true; +} + +bool +JSON::getBool(bool& value) const +{ + auto v = dynamic_cast(this->m->value.getPointer()); + if (v == nullptr) + { + return false; + } + value = v->value; + return true; +} + +bool +JSON::isNull() const +{ + if (dynamic_cast(this->m->value.getPointer())) + { + return true; + } + return false; +} + +bool +JSON::forEachDictItem( + std::function fn) const +{ + auto v = dynamic_cast(this->m->value.getPointer()); + if (v == nullptr) + { + return false; + } + for (auto const& k: v->members) + { + fn(k.first, JSON(k.second)); + } + return true; +} + +bool +JSON::forEachArrayItem(std::function fn) const +{ + auto v = dynamic_cast(this->m->value.getPointer()); + if (v == nullptr) + { + return false; + } + for (auto const& i: v->elements) + { + fn(JSON(i)); + } + return true; +} + bool JSON::checkSchema(JSON schema, std::list& errors) { diff --git a/libqpdf/JSONHandler.cc b/libqpdf/JSONHandler.cc new file mode 100644 index 00000000..7318466f --- /dev/null +++ b/libqpdf/JSONHandler.cc @@ -0,0 +1,160 @@ +#include +#include +#include + +JSONHandler::Error::Error(std::string const& msg) : + std::runtime_error(msg) +{ +} + +JSONHandler::JSONHandler() : + m(new Members()) +{ +} + +JSONHandler::Members::Members() +{ +} + +void +JSONHandler::addAnyHandler(json_handler_t fn) +{ + this->m->h.any_handler = fn; +} + +void +JSONHandler::addNullHandler(void_handler_t fn) +{ + this->m->h.null_handler = fn; +} + +void +JSONHandler::addStringHandler(string_handler_t fn) +{ + this->m->h.string_handler = fn; +} + +void +JSONHandler::addNumberHandler(string_handler_t fn) +{ + this->m->h.number_handler = fn; +} + +void +JSONHandler::addBoolHandler(bool_handler_t fn) +{ + this->m->h.bool_handler = fn; +} + +std::map>& +JSONHandler::addDictHandlers() +{ + return this->m->h.dict_handlers; +} + +void +JSONHandler::addFallbackDictHandler(std::shared_ptr fdh) +{ + this->m->h.fallback_dict_handler = fdh; +} + +void +JSONHandler::addArrayHandler(std::shared_ptr ah) +{ + this->m->h.array_handler = ah; +} + +void +JSONHandler::handle(std::string const& path, JSON j) +{ + if (this->m->h.any_handler) + { + this->m->h.any_handler(path, j); + return; + } + bool handled = false; + bool bvalue = false; + std::string svalue; + if (this->m->h.null_handler && j.isNull()) + { + this->m->h.null_handler(path); + handled = true; + } + if (this->m->h.string_handler && j.getString(svalue)) + { + this->m->h.string_handler(path, svalue); + handled = true; + } + if (this->m->h.number_handler && j.getNumber(svalue)) + { + this->m->h.number_handler(path, svalue); + handled = true; + } + if (this->m->h.bool_handler && j.getBool(bvalue)) + { + this->m->h.bool_handler(path, bvalue); + handled = true; + } + if ((this->m->h.fallback_dict_handler.get() || + (! this->m->h.dict_handlers.empty())) && j.isDictionary()) + { + std::string path_base = path; + if (path_base != ".") + { + path_base += "."; + } + j.forEachDictItem([&path, &path_base, this]( + std::string const& k, JSON v) { + auto i = this->m->h.dict_handlers.find(k); + if (i == this->m->h.dict_handlers.end()) + { + if (this->m->h.fallback_dict_handler.get()) + { + this->m->h.fallback_dict_handler->handle( + path_base + k, v); + } + else + { + QTC::TC("libtests", "JSONHandler unexpected key"); + throw Error( + "JSON handler found unexpected key " + k + + " in object at " + path); + } + } + else + { + i->second->handle(path_base + k, v); + } + }); + + // Set handled = true even if we didn't call any handlers. + // This dictionary could have been empty, but it's okay since + // it's a dictionary like it's supposed to be. + handled = true; + } + if (this->m->h.array_handler.get()) + { + size_t i = 0; + j.forEachArrayItem([&i, &path, this](JSON v) { + this->m->h.array_handler->handle( + path + "[" + QUtil::uint_to_string(i) + "]", v); + ++i; + }); + // Set handled = true even if we didn't call any handlers. + // This could have been an empty array. + handled = true; + } + + if (! handled) + { + // It would be nice to include information about what type the + // object was and what types were allowed, but we're relying + // on schema validation to make sure input is properly + // structured before calling the handlers. It would be + // different if this code were trying to be part of a + // general-purpose JSON package. + QTC::TC("libtests", "JSONHandler unhandled value"); + throw Error("JSON handler: value at " + path + + " is not of expected type"); + } +} diff --git a/libqpdf/build.mk b/libqpdf/build.mk index 4884a692..66d85176 100644 --- a/libqpdf/build.mk +++ b/libqpdf/build.mk @@ -38,6 +38,7 @@ SRCS_libqpdf = \ libqpdf/InputSource.cc \ libqpdf/InsecureRandomDataProvider.cc \ libqpdf/JSON.cc \ + libqpdf/JSONHandler.cc \ libqpdf/MD5.cc \ libqpdf/NNTree.cc \ libqpdf/OffsetInputSource.cc \ -- cgit v1.2.3-70-g09d2