diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index 33211f14..502f2346 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -14,7 +14,7 @@ // This chart shows an example of the state transitions that would occur in parsing a minimal file. -// | st_initial +// | // { | -> st_top // "qpdf": [ | -> st_qpdf // { | -> st_qpdf_meta @@ -47,7 +47,7 @@ // } | <- st_objects // } | <- st_qpdf // ] | <- st_top -// } | <- st_initial +// } | static char const* JSON_PDF = ( // force line break @@ -99,7 +99,7 @@ is_indirect_object(std::string const& v, int& obj, int& gen) } obj = QUtil::string_to_int(o_str.c_str()); gen = QUtil::string_to_int(g_str.c_str()); - return true; + return obj > 0; } static bool @@ -256,7 +256,6 @@ class QPDF::JSONReactor: public JSON::Reactor private: enum state_e { - st_initial, st_top, st_qpdf, st_qpdf_meta, @@ -268,28 +267,35 @@ class QPDF::JSONReactor: public JSON::Reactor st_ignore, }; + struct StackFrame + { + StackFrame(state_e state) : + state(state){}; + StackFrame(state_e state, QPDFObjectHandle&& object) : + state(state), + object(object){}; + state_e state; + QPDFObjectHandle object; + }; + void containerStart(); - void nestedState(std::string const& key, JSON const& value, state_e); + bool setNextStateIfDictionary(std::string const& key, JSON const& value, state_e); void setObjectDescription(QPDFObjectHandle& oh, JSON const& value); QPDFObjectHandle makeObject(JSON const& value); void error(qpdf_offset_t offset, std::string const& message); - void - replaceObject(QPDFObjectHandle to_replace, QPDFObjectHandle replacement, JSON const& value); + void replaceObject(QPDFObjectHandle&& replacement, JSON const& value); QPDF& pdf; std::shared_ptr is; bool must_be_complete{true}; std::shared_ptr descr; bool errors{false}; - bool parse_error{false}; bool saw_qpdf{false}; bool saw_qpdf_meta{false}; bool saw_objects{false}; bool saw_json_version{false}; bool saw_pdf_version{false}; bool saw_trailer{false}; - state_e state{st_initial}; - state_e next_state{st_top}; std::string cur_object; bool saw_value{false}; bool saw_stream{false}; @@ -297,9 +303,10 @@ class QPDF::JSONReactor: public JSON::Reactor bool saw_data{false}; bool saw_datafile{false}; bool this_stream_needs_data{false}; - std::vector state_stack{st_initial}; - std::vector object_stack; std::set reserved; + std::vector stack; + QPDFObjectHandle next_obj; + state_e next_state{st_top}; }; void @@ -322,8 +329,12 @@ QPDF::JSONReactor::anyErrors() const void QPDF::JSONReactor::containerStart() { - state_stack.push_back(state); - state = next_state; + if (next_obj.isInitialized()) { + stack.emplace_back(next_state, std::move(next_obj)); + next_obj = QPDFObjectHandle(); + } else { + stack.emplace_back(next_state); + } } void @@ -335,20 +346,19 @@ QPDF::JSONReactor::dictionaryStart() void QPDF::JSONReactor::arrayStart() { - containerStart(); - if (state == st_top) { + if (stack.empty()) { QTC::TC("qpdf", "QPDF_json top-level array"); throw std::runtime_error("QPDF JSON must be a dictionary"); } + containerStart(); } void QPDF::JSONReactor::containerEnd(JSON const& value) { - auto from_state = state; - state = state_stack.back(); - state_stack.pop_back(); - if (state == st_initial) { + auto from_state = stack.back().state; + stack.pop_back(); + if (stack.empty()) { if (!this->saw_qpdf) { QTC::TC("qpdf", "QPDF_json missing qpdf"); error(0, "\"qpdf\" object was not seen"); @@ -371,26 +381,16 @@ QPDF::JSONReactor::containerEnd(JSON const& value) } } } - } else if (state == st_objects) { - if (parse_error) { - QTC::TC("qpdf", "QPDF_json don't check object after parse error"); - } else if (cur_object == "trailer") { - if (!saw_value) { - QTC::TC("qpdf", "QPDF_json trailer no value"); - error(value.getStart(), "\"trailer\" is missing \"value\""); - } - } else if (saw_value == saw_stream) { + } else if (from_state == st_trailer) { + if (!saw_value) { + QTC::TC("qpdf", "QPDF_json trailer no value"); + error(value.getStart(), "\"trailer\" is missing \"value\""); + } + } else if (from_state == st_object_top) { + if (saw_value == saw_stream) { QTC::TC("qpdf", "QPDF_json value stream both or neither"); error(value.getStart(), "object must have exactly one of \"value\" or \"stream\""); } - object_stack.clear(); - this->cur_object = ""; - this->saw_dict = false; - this->saw_data = false; - this->saw_datafile = false; - this->saw_value = false; - this->saw_stream = false; - } else if (state == st_object_top) { if (saw_stream) { if (!saw_dict) { QTC::TC("qpdf", "QPDF_json stream no dict"); @@ -414,11 +414,7 @@ QPDF::JSONReactor::containerEnd(JSON const& value) } } } - } else if ((state == st_stream) || (state == st_object)) { - if (!parse_error) { - object_stack.pop_back(); - } - } else if ((state == st_top) && (from_state == st_qpdf)) { + } else if (from_state == st_qpdf) { // Handle dangling indirect object references which the PDF spec says to treat as nulls. // It's tempting to make this an error, but that would be wrong since valid input files may // have these. @@ -429,16 +425,27 @@ QPDF::JSONReactor::containerEnd(JSON const& value) } } } + if (!stack.empty()) { + auto state = stack.back().state; + if (state == st_objects) { + this->cur_object = ""; + this->saw_dict = false; + this->saw_data = false; + this->saw_datafile = false; + this->saw_value = false; + this->saw_stream = false; + } + } } void -QPDF::JSONReactor::replaceObject( - QPDFObjectHandle to_replace, QPDFObjectHandle replacement, JSON const& value) +QPDF::JSONReactor::replaceObject(QPDFObjectHandle&& replacement, JSON const& value) { - auto og = to_replace.getObjGen(); + auto& tos = stack.back(); + auto og = tos.object.getObjGen(); this->pdf.replaceObject(og, replacement); - auto oh = pdf.getObject(og); - setObjectDescription(oh, value); + next_obj = pdf.getObject(og); + setObjectDescription(tos.object, value); } void @@ -448,22 +455,26 @@ QPDF::JSONReactor::topLevelScalar() throw std::runtime_error("QPDF JSON must be a dictionary"); } -void -QPDF::JSONReactor::nestedState(std::string const& key, JSON const& value, state_e next) +bool +QPDF::JSONReactor::setNextStateIfDictionary(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()) { this->next_state = next; - } else { - error(value.getStart(), "\"" + key + "\" must be a dictionary"); - this->next_state = st_ignore; - this->parse_error = true; + return true; } + error(value.getStart(), "\"" + key + "\" must be a dictionary"); + return false; } bool QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) { + if (stack.empty()) { + throw std::logic_error("stack is empty in dictionaryItem"); + } + next_state = st_ignore; + auto state = stack.back().state; if (state == st_ignore) { QTC::TC("qpdf", "QPDF_json ignoring in st_ignore"); // ignore @@ -473,51 +484,48 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) if (!value.isArray()) { QTC::TC("qpdf", "QPDF_json qpdf not array"); error(value.getStart(), "\"qpdf\" must be an array"); - next_state = st_ignore; - parse_error = true; } else { next_state = st_qpdf; } } else { // Ignore all other fields. QTC::TC("qpdf", "QPDF_json ignoring unknown top-level key"); - next_state = st_ignore; } } else if (state == st_qpdf_meta) { if (key == "pdfversion") { this->saw_pdf_version = true; - bool version_okay = false; std::string v; + bool okay = false; if (value.getString(v)) { std::string version; char const* p = v.c_str(); if (QPDF::validatePDFVersion(p, version) && (*p == '\0')) { - version_okay = true; this->pdf.m->pdf_version = version; + okay = true; } } - if (!version_okay) { + if (!okay) { QTC::TC("qpdf", "QPDF_json bad pdf version"); - error(value.getStart(), "invalid PDF version (must be x.y)"); + error(value.getStart(), "invalid PDF version (must be \"x.y\")"); } } else if (key == "jsonversion") { this->saw_json_version = true; - bool version_okay = false; std::string v; + bool okay = false; if (value.getNumber(v)) { std::string version; if (QUtil::string_to_int(v.c_str()) == 2) { - version_okay = true; + okay = true; } } - if (!version_okay) { + if (!okay) { QTC::TC("qpdf", "QPDF_json bad json version"); - error(value.getStart(), "invalid JSON version (must be 2)"); + error(value.getStart(), "invalid JSON version (must be numeric value 2)"); } } else if (key == "pushedinheritedpageresources") { bool v; if (value.getBool(v)) { - if ((!this->must_be_complete) && v) { + if (!this->must_be_complete && v) { this->pdf.pushInheritedAttributesToPage(); } } else { @@ -527,7 +535,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) } else if (key == "calledgetallpages") { bool v; if (value.getBool(v)) { - if ((!this->must_be_complete) && v) { + if (!this->must_be_complete && v) { this->pdf.getAllPages(); } } else { @@ -538,103 +546,95 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) // ignore unknown keys for forward compatibility and to skip keys we don't care about // like "maxobjectid". QTC::TC("qpdf", "QPDF_json ignore second-level key"); - next_state = st_ignore; } } else if (state == st_objects) { int obj = 0; int gen = 0; if (key == "trailer") { this->saw_trailer = true; - nestedState(key, value, st_trailer); this->cur_object = "trailer"; + setNextStateIfDictionary(key, value, st_trailer); } else if (is_obj_key(key, obj, gen)) { this->cur_object = key; - auto oh = pdf.reserveObjectIfNotExists(QPDFObjGen(obj, gen)); - object_stack.push_back(oh); - nestedState(key, value, st_object_top); + if (setNextStateIfDictionary(key, value, st_object_top)) { + next_obj = pdf.reserveObjectIfNotExists(QPDFObjGen(obj, gen)); + } } else { QTC::TC("qpdf", "QPDF_json bad object key"); error(value.getStart(), "object key should be \"trailer\" or \"obj:n n R\""); - next_state = st_ignore; - parse_error = true; } } else if (state == st_object_top) { - if (object_stack.size() == 0) { - throw std::logic_error("no object on stack in st_object_top"); + if (stack.empty()) { + throw std::logic_error("stack empty in st_object_top"); + } + auto& tos = stack.back(); + if (!tos.object.isInitialized()) { + throw std::logic_error("current object uninitialized in st_object_top"); } - auto tos = object_stack.back(); - QPDFObjectHandle replacement; if (key == "value") { - // Don't use nestedState since this can have any type. + // Don't use setNextStateIfDictionary since this can have any type. this->saw_value = true; + replaceObject(makeObject(value), value); next_state = st_object; - replacement = makeObject(value); - replaceObject(tos, replacement, value); } else if (key == "stream") { this->saw_stream = true; - nestedState(key, value, st_stream); - this->this_stream_needs_data = false; - if (tos.isStream()) { - QTC::TC("qpdf", "QPDF_json updating existing stream"); + if (setNextStateIfDictionary(key, value, st_stream)) { + this->this_stream_needs_data = false; + if (tos.object.isStream()) { + QTC::TC("qpdf", "QPDF_json updating existing stream"); + } else { + this->this_stream_needs_data = true; + replaceObject(pdf.reserveStream(tos.object.getObjGen()), value); + } + next_obj = tos.object; } else { - this->this_stream_needs_data = true; - replacement = pdf.reserveStream(tos.getObjGen()); - replaceObject(tos, replacement, value); + // Error message already given above + QTC::TC("qpdf", "QPDF_json stream not a dictionary"); } } else { // Ignore unknown keys for forward compatibility QTC::TC("qpdf", "QPDF_json ignore unknown key in object_top"); - next_state = st_ignore; - } - if (replacement.isInitialized()) { - object_stack.pop_back(); - object_stack.push_back(replacement); } } else if (state == st_trailer) { if (key == "value") { this->saw_value = true; - // The trailer must be a dictionary, so we can use nestedState. - nestedState("trailer.value", value, st_object); - this->pdf.m->trailer = makeObject(value); - setObjectDescription(this->pdf.m->trailer, value); + // The trailer must be a dictionary, so we can use setNextStateIfDictionary. + if (setNextStateIfDictionary("trailer.value", value, st_object)) { + this->pdf.m->trailer = makeObject(value); + setObjectDescription(this->pdf.m->trailer, value); + } } else if (key == "stream") { // Don't need to set saw_stream here since there's already an error. QTC::TC("qpdf", "QPDF_json trailer stream"); error(value.getStart(), "the trailer may not be a stream"); - next_state = st_ignore; - parse_error = true; } else { // Ignore unknown keys for forward compatibility QTC::TC("qpdf", "QPDF_json ignore unknown key in trailer"); - next_state = st_ignore; } } else if (state == st_stream) { - if (object_stack.size() == 0) { - throw std::logic_error("no object on stack in st_stream"); + if (stack.empty()) { + throw std::logic_error("stack empty in st_stream"); } - auto tos = object_stack.back(); - if (!tos.isStream()) { - throw std::logic_error("top of stack is not stream in st_stream"); + auto& tos = stack.back(); + if (!tos.object.isStream()) { + throw std::logic_error("current object is not stream in st_stream"); } auto uninitialized = QPDFObjectHandle(); if (key == "dict") { this->saw_dict = true; - // Since a stream dictionary must be a dictionary, we can use nestedState to transition - // to st_value. - nestedState("stream.dict", value, st_object); - auto dict = makeObject(value); - if (dict.isDictionary()) { - tos.replaceDict(dict); + if (setNextStateIfDictionary("stream.dict", value, st_object)) { + tos.object.replaceDict(makeObject(value)); } else { - // An error had already been given by nestedState + // An error had already been given by setNextStateIfDictionary QTC::TC("qpdf", "QPDF_json stream dict not dict"); - parse_error = true; } } else if (key == "data") { this->saw_data = true; std::string v; if (!value.getString(v)) { + QTC::TC("qpdf", "QPDF_json stream data not string"); error(value.getStart(), "\"stream.data\" must be a string"); + tos.object.replaceStreamData("", uninitialized, uninitialized); } else { // The range includes the quotes. auto start = value.getStart() + 1; @@ -642,34 +642,42 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) if (end < start) { throw std::logic_error("QPDF_json: JSON string length < 0"); } - tos.replaceStreamData(provide_data(is, start, end), uninitialized, uninitialized); + tos.object.replaceStreamData( + provide_data(is, start, end), uninitialized, uninitialized); } } else if (key == "datafile") { this->saw_datafile = true; std::string filename; - if (value.getString(filename)) { - tos.replaceStreamData(QUtil::file_provider(filename), uninitialized, uninitialized); - } else { + if (!value.getString(filename)) { + QTC::TC("qpdf", "QPDF_json stream datafile not string"); error( value.getStart(), - "\"stream.datafile\" must be a string containing a file " - "name"); + "\"stream.datafile\" must be a string containing a file name"); + tos.object.replaceStreamData("", uninitialized, uninitialized); + } else { + tos.object.replaceStreamData( + QUtil::file_provider(filename), uninitialized, uninitialized); } } else { // Ignore unknown keys for forward compatibility. QTC::TC("qpdf", "QPDF_json ignore unknown key in stream"); - next_state = st_ignore; } } else if (state == st_object) { - if (!parse_error) { - auto dict = object_stack.back(); - if (dict.isStream()) { - dict = dict.getDict(); - } - dict.replaceKey( - is_pdf_name(key) ? QPDFObjectHandle::parse(key.substr(2)).getName() : key, - makeObject(value)); + if (stack.empty()) { + throw std::logic_error("stack empty in st_object"); } + auto& tos = stack.back(); + auto dict = tos.object; + if (dict.isStream()) { + dict = dict.getDict(); + } + if (!dict.isDictionary()) { + throw std::logic_error( + "current object is not stream or dictionary in st_object dictionary item"); + } + dict.replaceKey( + is_pdf_name(key) ? QPDFObjectHandle::parse(key.substr(2)).getName() : key, + makeObject(value)); } else { throw std::logic_error("QPDF_json: unknown state " + std::to_string(state)); } @@ -679,25 +687,24 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) bool QPDF::JSONReactor::arrayItem(JSON const& value) { + if (stack.empty()) { + throw std::logic_error("stack is empty in arrayItem"); + } + next_state = st_ignore; + auto state = stack.back().state; if (state == st_qpdf) { if (!this->saw_qpdf_meta) { this->saw_qpdf_meta = true; - nestedState("qpdf[0]", value, st_qpdf_meta); + setNextStateIfDictionary("qpdf[0]", value, st_qpdf_meta); } else if (!this->saw_objects) { this->saw_objects = true; - nestedState("qpdf[1]", value, st_objects); + setNextStateIfDictionary("qpdf[1]", value, st_objects); } else { QTC::TC("qpdf", "QPDF_json more than two qpdf elements"); error(value.getStart(), "\"qpdf\" must have two elements"); - next_state = st_ignore; - parse_error = true; - } - } - if (state == st_object) { - if (!parse_error) { - auto tos = object_stack.back(); - tos.appendItem(makeObject(value)); } + } else if (state == st_object) { + stack.back().object.appendItem(makeObject(value)); } return true; } @@ -722,10 +729,12 @@ QPDF::JSONReactor::makeObject(JSON const& value) bool bool_v = false; if (value.isDictionary()) { result = QPDFObjectHandle::newDictionary(); - object_stack.push_back(result); + next_obj = result; + next_state = st_object; } else if (value.isArray()) { result = QPDFObjectHandle::newArray(); - object_stack.push_back(result); + next_obj = result; + next_state = st_object; } else if (value.isNull()) { result = QPDFObjectHandle::newNull(); } else if (value.getBool(bool_v)) { diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 51c3ea72..7501a4b5 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -668,7 +668,6 @@ QPDF_json value stream both or neither 0 QPDFJob need json-stream-prefix for stdout 0 QPDFJob write json to stdout 0 QPDFJob write json to file 0 -QPDF_json don't check object after parse error 0 QPDF_json ignoring unknown top-level key 0 QPDF_json ignore second-level key 0 QPDF_json ignore unknown key in object_top 0 @@ -694,3 +693,6 @@ QPDFJob misplaced page range 0 QPDFJob duplicated range 0 QPDFJob json over/under no file 0 QPDF_Array copy 1 +QPDF_json stream data not string 0 +QPDF_json stream datafile not string 0 +QPDF_json stream not a dictionary 0 diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test index 0ea126ec..2f7bcd86 100644 --- a/qpdf/qtest/qpdf-json.test +++ b/qpdf/qtest/qpdf-json.test @@ -37,6 +37,8 @@ my @badfiles = ( 'obj-key-errors', 'bad-data', 'bad-datafile', + 'bad-data2', + 'bad-datafile2', ); $n_tests += scalar(@badfiles); diff --git a/qpdf/qtest/qpdf/qjson-bad-data2.json b/qpdf/qtest/qpdf/qjson-bad-data2.json new file mode 100644 index 00000000..80206086 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-data2.json @@ -0,0 +1,71 @@ +{ + "qpdf": [ + { + "jsonversion": 2, + "pdfversion": "1.3", + "maxobjectid": 6 + }, + { + "obj:1 0 R": { + "value": { + "/Pages": "2 0 R", + "/Type": "/Catalog" + } + }, + "obj:2 0 R": { + "value": { + "/Count": 1, + "/Kids": [ + "3 0 R" + ], + "/Type": "/Pages" + } + }, + "obj:3 0 R": { + "value": { + "/Contents": ["4 0 R", "7 0 R"], + "/MediaBox": [ + 0, + 0, + 612, + 792 + ], + "/Parent": "2 0 R", + "/Resources": { + "/Font": { + "/F1": "6 0 R" + }, + "/ProcSet": "5 0 R" + }, + "/Type": "/Page" + } + }, + "obj:4 0 R": { + "stream": { + "data": [[]], + "dict": {} + } + }, + "obj:5 0 R": { + "value": [ + "/PDF", + "/Text" + ] + }, + "obj:6 0 R": { + "value": { + "/BaseFont": "/Helvetica", + "/Encoding": "/WinAnsiEncoding", + "/Subtype": "/Type1", + "/Type": "/Font" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + ] +} diff --git a/qpdf/qtest/qpdf/qjson-bad-data2.out b/qpdf/qtest/qpdf/qjson-bad-data2.out new file mode 100644 index 00000000..47c83c8e --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-data2.out @@ -0,0 +1,2 @@ +WARNING: qjson-bad-data2.json (obj:4 0 R, offset 846): "stream.data" must be a string +qpdf: qjson-bad-data2.json: errors found in JSON diff --git a/qpdf/qtest/qpdf/qjson-bad-datafile2.json b/qpdf/qtest/qpdf/qjson-bad-datafile2.json new file mode 100644 index 00000000..b5c99820 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-datafile2.json @@ -0,0 +1,71 @@ +{ + "qpdf": [ + { + "jsonversion": 2, + "pdfversion": "1.3", + "maxobjectid": 6 + }, + { + "obj:1 0 R": { + "value": { + "/Pages": "2 0 R", + "/Type": "/Catalog" + } + }, + "obj:2 0 R": { + "value": { + "/Count": 1, + "/Kids": [ + "3 0 R" + ], + "/Type": "/Pages" + } + }, + "obj:3 0 R": { + "value": { + "/Contents": ["4 0 R", "7 0 R"], + "/MediaBox": [ + 0, + 0, + 612, + 792 + ], + "/Parent": "2 0 R", + "/Resources": { + "/Font": { + "/F1": "6 0 R" + }, + "/ProcSet": "5 0 R" + }, + "/Type": "/Page" + } + }, + "obj:4 0 R": { + "stream": { + "datafile": [[]], + "dict": {} + } + }, + "obj:5 0 R": { + "value": [ + "/PDF", + "/Text" + ] + }, + "obj:6 0 R": { + "value": { + "/BaseFont": "/Helvetica", + "/Encoding": "/WinAnsiEncoding", + "/Subtype": "/Type1", + "/Type": "/Font" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + ] +} diff --git a/qpdf/qtest/qpdf/qjson-bad-datafile2.out b/qpdf/qtest/qpdf/qjson-bad-datafile2.out new file mode 100644 index 00000000..41949a21 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-datafile2.out @@ -0,0 +1,2 @@ +WARNING: qjson-bad-datafile2.json (obj:4 0 R, offset 850): "stream.datafile" must be a string containing a file name +qpdf: qjson-bad-datafile2.json: errors found in JSON diff --git a/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out b/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out index f364f1a6..476128e7 100644 --- a/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out +++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out @@ -1,3 +1,3 @@ -WARNING: qjson-bad-pdf-version1.json (offset 41): invalid JSON version (must be 2) -WARNING: qjson-bad-pdf-version1.json (offset 70): invalid PDF version (must be x.y) +WARNING: qjson-bad-pdf-version1.json (offset 41): invalid JSON version (must be numeric value 2) +WARNING: qjson-bad-pdf-version1.json (offset 70): invalid PDF version (must be "x.y") qpdf: qjson-bad-pdf-version1.json: errors found in JSON diff --git a/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out b/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out index 9bc88ff4..cb914414 100644 --- a/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out +++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out @@ -1,5 +1,5 @@ -WARNING: qjson-bad-pdf-version2.json (offset 41): invalid JSON version (must be 2) -WARNING: qjson-bad-pdf-version2.json (offset 66): invalid PDF version (must be x.y) +WARNING: qjson-bad-pdf-version2.json (offset 41): invalid JSON version (must be numeric value 2) +WARNING: qjson-bad-pdf-version2.json (offset 66): invalid PDF version (must be "x.y") WARNING: qjson-bad-pdf-version2.json (offset 97): calledgetallpages must be a boolean WARNING: qjson-bad-pdf-version2.json (offset 138): pushedinheritedpageresources must be a boolean qpdf: qjson-bad-pdf-version2.json: errors found in JSON diff --git a/qpdf/qtest/qpdf/qjson-obj-key-errors.out b/qpdf/qtest/qpdf/qjson-obj-key-errors.out index 0263f294..f1f0a369 100644 --- a/qpdf/qtest/qpdf/qjson-obj-key-errors.out +++ b/qpdf/qtest/qpdf/qjson-obj-key-errors.out @@ -1,7 +1,7 @@ WARNING: qjson-obj-key-errors.json (obj:2 0 R, offset 244): object must have exactly one of "value" or "stream" WARNING: qjson-obj-key-errors.json (obj:3 0 R, offset 542): object must have exactly one of "value" or "stream" -WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 710): "stream" is missing "dict" -WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 710): new "stream" must have exactly one of "data" or "datafile" -WARNING: qjson-obj-key-errors.json (obj:5 0 R, offset 800): new "stream" must have exactly one of "data" or "datafile" +WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 690): "stream" is missing "dict" +WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 690): new "stream" must have exactly one of "data" or "datafile" +WARNING: qjson-obj-key-errors.json (obj:5 0 R, offset 780): new "stream" must have exactly one of "data" or "datafile" WARNING: qjson-obj-key-errors.json (trailer, offset 1178): "trailer" is missing "value" qpdf: qjson-obj-key-errors.json: errors found in JSON diff --git a/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out index a264839f..04df1518 100644 --- a/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out +++ b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out @@ -1,5 +1,4 @@ WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 142): "stream.dict" must be a dictionary -WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 142): unrecognized string value -WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 122): new "stream" must have exactly one of "data" or "datafile" +WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 102): new "stream" must have exactly one of "data" or "datafile" WARNING: qjson-stream-dict-not-dict.json: "qpdf[1].trailer" was not seen qpdf: qjson-stream-dict-not-dict.json: errors found in JSON diff --git a/qpdf/qtest/qpdf/qjson-stream-not-dict.out b/qpdf/qtest/qpdf/qjson-stream-not-dict.out index fbd953c6..db775b59 100644 --- a/qpdf/qtest/qpdf/qjson-stream-not-dict.out +++ b/qpdf/qtest/qpdf/qjson-stream-not-dict.out @@ -1,3 +1,4 @@ WARNING: qjson-stream-not-dict.json (obj:1 0 R, offset 122): "stream" must be a dictionary +WARNING: qjson-stream-not-dict.json (obj:1 0 R, offset 102): "stream" is missing "dict" WARNING: qjson-stream-not-dict.json: "qpdf[1].trailer" was not seen qpdf: qjson-stream-not-dict.json: errors found in JSON diff --git a/qpdf/qtest/qpdf/qjson-trailer-stream.out b/qpdf/qtest/qpdf/qjson-trailer-stream.out index a625cd6d..fccb2a39 100644 --- a/qpdf/qtest/qpdf/qjson-trailer-stream.out +++ b/qpdf/qtest/qpdf/qjson-trailer-stream.out @@ -1,2 +1,3 @@ WARNING: qjson-trailer-stream.json (trailer, offset 1269): the trailer may not be a stream +WARNING: qjson-trailer-stream.json (trailer, offset 1249): "trailer" is missing "value" qpdf: qjson-trailer-stream.json: errors found in JSON diff --git a/qpdf/qtest/qpdf/update-from-json-errors.out b/qpdf/qtest/qpdf/update-from-json-errors.out index 530d707d..5e136c55 100644 --- a/qpdf/qtest/qpdf/update-from-json-errors.out +++ b/qpdf/qtest/qpdf/update-from-json-errors.out @@ -1,4 +1,4 @@ -WARNING: good13.pdf (obj:4 0 R from qpdf-json-update-errors.json, offset 95): existing "stream" may at most one of "data" or "datafile" +WARNING: good13.pdf (obj:4 0 R from qpdf-json-update-errors.json, offset 75): existing "stream" may at most one of "data" or "datafile" WARNING: good13.pdf (obj:20 0 R from qpdf-json-update-errors.json, offset 335): unrecognized string value -WARNING: good13.pdf (obj:20 0 R from qpdf-json-update-errors.json, offset 293): new "stream" must have exactly one of "data" or "datafile" +WARNING: good13.pdf (obj:20 0 R from qpdf-json-update-errors.json, offset 273): new "stream" must have exactly one of "data" or "datafile" qpdf: qpdf-json-update-errors.json: errors found in JSON