#include #include #include #include #include namespace { class JSONExc: public std::runtime_error { public: JSONExc(JSON const& value, std::string const& msg) : std::runtime_error( "offset " + QUtil::uint_to_string(value.getStart()) + ": " + msg) { } }; } // namespace static std::regex PDF_VERSION_RE("^\\d+\\.\\d+$"); static std::regex OBJ_KEY_RE("^obj:(\\d+) (\\d+) R$"); QPDF::JSONReactor::JSONReactor(QPDF& pdf, bool must_be_complete) : pdf(pdf), must_be_complete(must_be_complete), saw_qpdf(false), saw_json_version(false), saw_pdf_version(false), saw_trailer(false), state(st_initial), next_state(st_top) { state_stack.push_back(st_initial); } void QPDF::JSONReactor::containerStart() { state_stack.push_back(state); state = next_state; } void QPDF::JSONReactor::dictionaryStart() { containerStart(); // QXXXQ } void QPDF::JSONReactor::arrayStart() { containerStart(); if (state == st_top) { QTC::TC("qpdf", "QPDF_json top-level array"); throw std::runtime_error("QPDF JSON must be a dictionary"); } // QXXXQ } void QPDF::JSONReactor::containerEnd(JSON const& value) { state = state_stack.back(); state_stack.pop_back(); if (state == st_initial) { if (!this->saw_qpdf) { QTC::TC("qpdf", "QPDF_json missing qpdf"); throw std::runtime_error("\"qpdf\" object was not seen"); } if (!this->saw_json_version) { QTC::TC("qpdf", "QPDF_json missing json version"); throw std::runtime_error("\"qpdf.jsonversion\" was not seen"); } if (must_be_complete && !this->saw_pdf_version) { QTC::TC("qpdf", "QPDF_json missing pdf version"); throw std::runtime_error("\"qpdf.pdfversion\" was not seen"); } if (must_be_complete && !this->saw_trailer) { /// QTC::TC("qpdf", "QPDF_json missing trailer"); throw std::runtime_error("\"qpdf.objects.trailer\" was not seen"); } } // QXXXQ } void QPDF::JSONReactor::topLevelScalar() { QTC::TC("qpdf", "QPDF_json top-level scalar"); throw std::runtime_error("QPDF JSON must be a dictionary"); } void QPDF::JSONReactor::nestedState( std::string const& key, JSON const& value, state_e next) { // Use this method when the next state is for processing a nested // dictionary. if (!value.isDictionary()) { throw JSONExc(value, "\"" + key + "\" must be a dictionary"); } this->next_state = next; } bool QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) { if (state == st_ignore) { // ignore } else if (state == st_top) { if (key == "qpdf") { this->saw_qpdf = true; nestedState(key, value, st_qpdf); } else { // Ignore all other fields for forward compatibility. // Don't use nestedState since this can be any type. next_state = st_ignore; } } else if (state == st_qpdf) { if (key == "jsonversion") { this->saw_json_version = true; std::string v; if (!(value.getNumber(v) && (v == "2"))) { QTC::TC("qpdf", "QPDF_json bad json version"); throw JSONExc(value, "only JSON version 2 is supported"); } } else if (key == "pdfversion") { this->saw_pdf_version = true; bool version_okay = false; std::string v; if (value.getString(v)) { std::smatch m; if (std::regex_match(v, m, PDF_VERSION_RE)) { version_okay = true; this->pdf.m->pdf_version = v; } } if (!version_okay) { QTC::TC("qpdf", "QPDF_json bad pdf version"); throw JSONExc(value, "invalid PDF version (must be x.y)"); } } else if (key == "objects") { nestedState(key, value, st_objects_top); } else { // ignore unknown keys for forward compatibility } } else if (state == st_objects_top) { std::smatch m; if (key == "trailer") { this->saw_trailer = true; nestedState(key, value, st_trailer_top); // QXXXQ } else if (std::regex_match(key, m, OBJ_KEY_RE)) { nestedState(key, value, st_object_top); // QXXXQ } else { QTC::TC("qpdf", "QPDF_json bad object key"); throw JSONExc( value, "object key should be \"trailer\" or \"obj:n n R\""); } } else if (state == st_object_top) { if (key == "value") { // Don't use nestedState since this can have any type. next_state = st_object; // QXXXQ } else if (key == "stream") { nestedState(key, value, st_stream); // QXXXQ } else { // Ignore unknown keys for forward compatibility } } else if (state == st_trailer_top) { if (key == "value") { // The trailer must be a dictionary, so we can use nestedState. nestedState("trailer.value", value, st_object); // QXXXQ } else if (key == "stream") { QTC::TC("qpdf", "QPDF_json trailer stream"); throw JSONExc(value, "the trailer may not be a stream"); } else { // Ignore unknown keys for forward compatibility } } else if (state == st_stream) { if (key == "dict") { // Since a stream dictionary must be a dictionary, we can // use nestedState to transition to st_value. nestedState("stream.dict", value, st_object); // QXXXQ } else if (key == "data") { // QXXXQ } else if (key == "datafile") { // QXXXQ } else { // Ignore unknown keys for forward compatibility. next_state = st_ignore; } } else if (state == st_object) { // QXXXQ } else { throw std::logic_error( "QPDF_json: unknown state " + QUtil::int_to_string(state)); } // QXXXQ return true; } bool QPDF::JSONReactor::arrayItem(JSON const& value) { // QXXXQ return true; } void QPDF::createFromJSON(std::string const& json_file) { createFromJSON(std::make_shared(json_file.c_str())); } void QPDF::createFromJSON(std::shared_ptr is) { importJSON(is, true); } void QPDF::updateFromJSON(std::string const& json_file) { updateFromJSON(std::make_shared(json_file.c_str())); } void QPDF::updateFromJSON(std::shared_ptr is) { importJSON(is, false); } void QPDF::importJSON(std::shared_ptr is, bool must_be_complete) { JSONReactor reactor(*this, must_be_complete); try { JSON::parse(*is, &reactor); } catch (std::runtime_error& e) { throw std::runtime_error(is->getName() + ": " + e.what()); } }