aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO9
-rw-r--r--include/qpdf/QPDF.hh29
-rw-r--r--include/qpdf/QPDFObjectHandle.hh1
-rw-r--r--libqpdf/QPDF.cc7
-rw-r--r--libqpdf/QPDF_json.cc373
-rw-r--r--qpdf/qpdf.testcov9
-rw-r--r--qpdf/qtest/qpdf-json.test3
-rw-r--r--qpdf/qtest/qpdf/qjson-bad-json-version1.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-bad-json-version2.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-bad-object-key.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-bad-pdf-version1.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-bad-pdf-version2.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-missing-objects.json11
-rw-r--r--qpdf/qtest/qpdf/qjson-missing-objects.out2
-rw-r--r--qpdf/qtest/qpdf/qjson-missing-trailer.json67
-rw-r--r--qpdf/qtest/qpdf/qjson-missing-trailer.out2
-rw-r--r--qpdf/qtest/qpdf/qjson-no-json-version.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-no-pdf-version.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-no-qpdf-object.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-obj-key-errors.json64
-rw-r--r--qpdf/qtest/qpdf/qjson-obj-key-errors.out7
-rw-r--r--qpdf/qtest/qpdf/qjson-object-not-dict.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-objects-not-dict.out4
-rw-r--r--qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out6
-rw-r--r--qpdf/qtest/qpdf/qjson-stream-not-dict.out4
-rw-r--r--qpdf/qtest/qpdf/qjson-trailer-not-dict.json2
-rw-r--r--qpdf/qtest/qpdf/qjson-trailer-not-dict.out3
-rw-r--r--qpdf/qtest/qpdf/qjson-trailer-stream.json2
-rw-r--r--qpdf/qtest/qpdf/qjson-trailer-stream.out3
29 files changed, 562 insertions, 73 deletions
diff --git a/TODO b/TODO
index afc68a9c..64d537a1 100644
--- a/TODO
+++ b/TODO
@@ -54,6 +54,15 @@ Soon: Break ground on "Document-level work"
Output JSON v2
==============
+XXX
+
+* Reread from perspective of update
+* Test all ignore cases with QTC
+* Test case of correct file with dict before data/datafile
+* Have a test case if possible that exercises the object description
+ which means we need some kind of semantic error that gets caught
+ after creation.
+
Try to never flatten pages tree. Make sure we do something reasonable
with pages tree repair. The problem is that if pages tree repair is
done as a side effect of running --json, the qpdf part of the json may
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index b547fcbd..f3ce4684 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -998,7 +998,7 @@ class QPDF
class JSONReactor: public JSON::Reactor
{
public:
- JSONReactor(QPDF&, bool must_be_complete);
+ JSONReactor(QPDF&, std::string const& filename, bool must_be_complete);
virtual ~JSONReactor() = default;
virtual void dictionaryStart() override;
virtual void arrayStart() override;
@@ -1008,31 +1008,51 @@ class QPDF
dictionaryItem(std::string const& key, JSON const& value) override;
virtual bool arrayItem(JSON const& value) override;
+ bool anyErrors() const;
+
private:
enum state_e {
st_initial,
st_top,
- st_ignore,
st_qpdf,
- st_objects_top,
- st_trailer_top,
+ st_objects,
+ st_trailer,
st_object_top,
st_stream,
st_object,
+ st_ignore,
};
void containerStart();
void nestedState(std::string const& key, JSON const& value, state_e);
+ QPDFObjectHandle makeObject(JSON const& value);
+ void error(size_t offset, std::string const& message);
+ QPDFObjectHandle
+ reserveObject(std::string const& obj, std::string const& gen);
+ void replaceObject(
+ QPDFObjectHandle to_replace, QPDFObjectHandle replacement);
QPDF& pdf;
+ std::string filename;
bool must_be_complete;
+ bool errors;
+ bool parse_error;
bool saw_qpdf;
+ bool saw_objects;
bool saw_json_version;
bool saw_pdf_version;
bool saw_trailer;
state_e state;
state_e next_state;
+ std::string cur_object;
+ bool saw_value;
+ bool saw_stream;
+ bool saw_dict;
+ bool saw_data;
+ bool saw_datafile;
std::vector<state_e> state_stack;
+ std::vector<QPDFObjectHandle> object_stack;
+ std::set<QPDFObjGen> reserved;
};
friend class JSONReactor;
@@ -1080,6 +1100,7 @@ class QPDF
void resolveObjectsInStream(int obj_stream_number);
void stopOnError(std::string const& message);
QPDFObjectHandle reserveObjectIfNotExists(int objid, int gen);
+ QPDFObjectHandle reserveStream(int objid, int gen);
// Calls finish() on the pipeline when done but does not delete it
bool pipeStreamData(
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index ec4a9699..5bdafb31 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -1431,7 +1431,6 @@ class QPDFObjectHandle
{
return QPDFObjectHandle::newIndirect(qpdf, objid, generation);
}
- // object must be dictionary object
static QPDFObjectHandle
newStream(
QPDF* qpdf,
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index 1807a838..c353bc4c 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -2167,6 +2167,13 @@ QPDF::reserveObjectIfNotExists(int objid, int gen)
}
QPDFObjectHandle
+QPDF::reserveStream(int objid, int gen)
+{
+ return QPDFObjectHandle::Factory::newStream(
+ this, objid, gen, QPDFObjectHandle::newDictionary(), 0, 0);
+}
+
+QPDFObjectHandle
QPDF::getObjectByObjGen(QPDFObjGen const& og)
{
return getObjectByID(og.getObj(), og.getGen());
diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc
index 2f74a673..d71c75ba 100644
--- a/libqpdf/QPDF_json.cc
+++ b/libqpdf/QPDF_json.cc
@@ -1,41 +1,102 @@
#include <qpdf/QPDF.hh>
#include <qpdf/FileInputSource.hh>
+#include <qpdf/QIntC.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
#include <regex>
-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
+// This chart shows an example of the state transitions that would
+// occur in parsing a minimal file.
+
+// | st_initial
+// { | -> st_top
+// "qpdf": { | -> st_qpdf
+// "objects": { | -> st_objects
+// "obj:1 0 R": { | -> st_object_top
+// "value": { | -> st_object
+// "/Pages": "2 0 R", | ...
+// "/Type": "/Catalog" | ...
+// } | <- st_object_top
+// }, | <- st_objects
+// "obj:2 0 R": { | -> st_object_top
+// "value": 12 | -> st_object
+// } | <- st_object_top
+// }, | <- st_objects
+// "obj:4 0 R": { | -> st_object_top
+// "stream": { | -> st_stream
+// "data": "cG90YXRv", | ...
+// "dict": { | -> st_object
+// "/K": true | ...
+// } | <- st_stream
+// } | <- st_object_top
+// }, | <- st_objects
+// "trailer": { | -> st_trailer
+// "value": { | -> st_object
+// "/Root": "1 0 R", | ...
+// "/Size": 7 | ...
+// } | <- st_trailer
+// } | <- st_objects
+// } | <- st_qpdf
+// } | <- st_top
+// } | <- st_initial
+
+static char const* JSON_PDF = (
+ // force line break
+ "%PDF-1.3\n"
+ "xref\n"
+ "0 1\n"
+ "0000000000 65535 f \n"
+ "trailer << /Size 1 >>\n"
+ "startxref\n"
+ "9\n"
+ "%%EOF\n");
static std::regex PDF_VERSION_RE("^\\d+\\.\\d+$");
static std::regex OBJ_KEY_RE("^obj:(\\d+) (\\d+) R$");
+static std::regex INDIRECT_OBJ_RE("^(\\d+) (\\d+) R$");
+static std::regex UNICODE_RE("^u:(.*)$");
+static std::regex BINARY_RE("^b:((?:[0-9a-fA-F]{2})*)$");
+static std::regex NAME_RE("^/.*$");
-QPDF::JSONReactor::JSONReactor(QPDF& pdf, bool must_be_complete) :
+QPDF::JSONReactor::JSONReactor(
+ QPDF& pdf, std::string const& filename, bool must_be_complete) :
pdf(pdf),
+ filename(filename),
must_be_complete(must_be_complete),
+ errors(false),
+ parse_error(false),
saw_qpdf(false),
+ saw_objects(false),
saw_json_version(false),
saw_pdf_version(false),
saw_trailer(false),
state(st_initial),
- next_state(st_top)
+ next_state(st_top),
+ saw_value(false),
+ saw_stream(false),
+ saw_dict(false),
+ saw_data(false),
+ saw_datafile(false)
{
state_stack.push_back(st_initial);
}
void
+QPDF::JSONReactor::error(size_t offset, std::string const& msg)
+{
+ this->errors = true;
+ this->pdf.warn(
+ qpdf_e_json, this->cur_object, QIntC::to_offset(offset), msg);
+}
+
+bool
+QPDF::JSONReactor::anyErrors() const
+{
+ return this->errors;
+}
+
+void
QPDF::JSONReactor::containerStart()
{
state_stack.push_back(state);
@@ -46,7 +107,6 @@ void
QPDF::JSONReactor::dictionaryStart()
{
containerStart();
- // QXXXQ
}
void
@@ -57,7 +117,6 @@ QPDF::JSONReactor::arrayStart()
QTC::TC("qpdf", "QPDF_json top-level array");
throw std::runtime_error("QPDF JSON must be a dictionary");
}
- // QXXXQ
}
void
@@ -68,23 +127,102 @@ QPDF::JSONReactor::containerEnd(JSON const& value)
if (state == st_initial) {
if (!this->saw_qpdf) {
QTC::TC("qpdf", "QPDF_json missing qpdf");
- throw std::runtime_error("\"qpdf\" object was not seen");
+ error(0, "\"qpdf\" object was not seen");
+ } else {
+ if (!this->saw_json_version) {
+ QTC::TC("qpdf", "QPDF_json missing json version");
+ error(0, "\"qpdf.jsonversion\" was not seen");
+ }
+ if (must_be_complete && !this->saw_pdf_version) {
+ QTC::TC("qpdf", "QPDF_json missing pdf version");
+ error(0, "\"qpdf.pdfversion\" was not seen");
+ }
+ if (!this->saw_objects) {
+ QTC::TC("qpdf", "QPDF_json missing objects");
+ error(0, "\"qpdf.objects\" was not seen");
+ } else {
+ if (must_be_complete && !this->saw_trailer) {
+ QTC::TC("qpdf", "QPDF_json missing trailer");
+ error(0, "\"qpdf.objects.trailer\" 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");
+ } else if (state == st_objects) {
+ if (parse_error) {
+ // ignore
+ } 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) {
+ QTC::TC("qpdf", "QPDF_json value stream both or neither");
+ error(
+ value.getStart(),
+ "object must have exactly one of \"value\" or \"stream\"");
}
- 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");
+ 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");
+ error(value.getStart(), "\"stream\" is missing \"dict\"");
+ }
+ if (must_be_complete) {
+ if (saw_data == saw_datafile) {
+ QTC::TC("qpdf", "QPDF_json data datafile both or neither");
+ error(
+ value.getStart(),
+ "\"stream\" must have exactly one of \"data\" or "
+ "\"datafile\"");
+ }
+ } else if (saw_data && saw_datafile) {
+ // QXXXQ
+ /// QTC::TC("qpdf", "QPDF_json data and datafile");
+ error(
+ value.getStart(),
+ "\"stream\" may at most one of \"data\" or \"datafile\"");
+ }
+ }
+ } else if ((state == st_stream) || (state == st_object)) {
+ if (!parse_error) {
+ object_stack.pop_back();
}
- if (must_be_complete && !this->saw_trailer) {
- /// QTC::TC("qpdf", "QPDF_json missing trailer");
- throw std::runtime_error("\"qpdf.objects.trailer\" was not seen");
+ } else if (state == st_qpdf) {
+ for (auto const& og: this->reserved) {
+ // QXXXQ
+ // QTC::TC("qpdf", "QPDF_json non-trivial null reserved");
+ this->pdf.replaceObject(og, QPDFObjectHandle::newNull());
}
+ this->reserved.clear();
}
+}
- // QXXXQ
+QPDFObjectHandle
+QPDF::JSONReactor::reserveObject(std::string const& obj, std::string const& gen)
+{
+ int o = QUtil::string_to_int(obj.c_str());
+ int g = QUtil::string_to_int(gen.c_str());
+ auto oh = pdf.reserveObjectIfNotExists(o, g);
+ if (oh.isReserved()) {
+ this->reserved.insert(QPDFObjGen(o, g));
+ }
+ return oh;
+}
+
+void
+QPDF::JSONReactor::replaceObject(
+ QPDFObjectHandle to_replace, QPDFObjectHandle replacement)
+{
+ auto og = to_replace.getObjGen();
+ this->reserved.erase(og);
+ this->pdf.replaceObject(og, replacement);
}
void
@@ -100,16 +238,20 @@ QPDF::JSONReactor::nestedState(
{
// Use this method when the next state is for processing a nested
// dictionary.
- if (!value.isDictionary()) {
- throw JSONExc(value, "\"" + key + "\" must be a 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;
}
- this->next_state = next;
}
bool
QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
{
if (state == st_ignore) {
+ QTC::TC("qpdf", "QPDF_json ignoring in st_ignore");
// ignore
} else if (state == st_top) {
if (key == "qpdf") {
@@ -118,6 +260,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
} else {
// Ignore all other fields for forward compatibility.
// Don't use nestedState since this can be any type.
+ // QXXXQ QTC
next_state = st_ignore;
}
} else if (state == st_qpdf) {
@@ -126,7 +269,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
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");
+ error(value.getStart(), "only JSON version 2 is supported");
}
} else if (key == "pdfversion") {
this->saw_pdf_version = true;
@@ -141,81 +284,197 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
}
if (!version_okay) {
QTC::TC("qpdf", "QPDF_json bad pdf version");
- throw JSONExc(value, "invalid PDF version (must be x.y)");
+ error(value.getStart(), "invalid PDF version (must be x.y)");
}
} else if (key == "objects") {
- nestedState(key, value, st_objects_top);
+ this->saw_objects = true;
+ nestedState(key, value, st_objects);
} else {
// ignore unknown keys for forward compatibility
+ // QXXXQ QTC
+ next_state = st_ignore;
}
- } else if (state == st_objects_top) {
+ } else if (state == st_objects) {
std::smatch m;
if (key == "trailer") {
this->saw_trailer = true;
- nestedState(key, value, st_trailer_top);
- // QXXXQ
+ nestedState(key, value, st_trailer);
+ this->cur_object = "trailer";
} else if (std::regex_match(key, m, OBJ_KEY_RE)) {
+ // QXXXQ remember to handle null for delete
+ object_stack.push_back(reserveObject(m[1].str(), m[2].str()));
nestedState(key, value, st_object_top);
- // QXXXQ
+ this->cur_object = key;
} else {
QTC::TC("qpdf", "QPDF_json bad object key");
- throw JSONExc(
- value, "object key should be \"trailer\" or \"obj:n n R\"");
+ 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");
+ }
+ auto tos = object_stack.back();
+ QPDFObjectHandle replacement;
if (key == "value") {
// Don't use nestedState since this can have any type.
+ this->saw_value = true;
next_state = st_object;
- // QXXXQ
+ replacement = makeObject(value);
+ replaceObject(tos, replacement);
} else if (key == "stream") {
+ this->saw_stream = true;
nestedState(key, value, st_stream);
- // QXXXQ
+ if (tos.isStream()) {
+ // QXXXQ reusing -- need QTC
+ } else {
+ replacement =
+ pdf.reserveStream(tos.getObjectID(), tos.getGeneration());
+ replaceObject(tos, replacement);
+ replacement.replaceStreamData(
+ "", "<<>>"_qpdf, "<<>>"_qpdf); // QXXXQ
+ }
} else {
// Ignore unknown keys for forward compatibility
+ // QXXXQ QTC
+ next_state = st_ignore;
}
- } else if (state == st_trailer_top) {
+ 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);
- // QXXXQ
+ this->pdf.m->trailer = makeObject(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");
- throw JSONExc(value, "the trailer may not be a 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
+ // QXXXQ QTC
+ next_state = st_ignore;
}
} else if (state == st_stream) {
- if (key == "dict") {
+ if (object_stack.size() == 0) {
+ throw std::logic_error("no object on stack in st_stream");
+ }
+ auto tos = object_stack.back();
+ if (!tos.isStream()) {
+ // QXXXQ QTC in update mode
+ error(value.getStart(), "this object is not a stream");
+ parse_error = true;
+ } else 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);
- // QXXXQ
+ auto dict = makeObject(value);
+ if (dict.isDictionary()) {
+ tos.replaceDict(dict);
+ } else {
+ // An error had already been given by nestedState
+ QTC::TC("qpdf", "QPDF_json stream dict not dict");
+ parse_error = true;
+ }
} else if (key == "data") {
+ this->saw_data = true;
// QXXXQ
} else if (key == "datafile") {
+ this->saw_datafile = true;
// QXXXQ
} else {
// Ignore unknown keys for forward compatibility.
+ // QXXXQ QTC
next_state = st_ignore;
}
} else if (state == st_object) {
- // QXXXQ
+ if (!parse_error) {
+ auto dict = object_stack.back();
+ if (dict.isStream()) {
+ dict = dict.getDict();
+ }
+ dict.replaceKey(key, makeObject(value));
+ }
} 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
+ if (state == st_object) {
+ if (!parse_error) {
+ auto tos = object_stack.back();
+ tos.appendItem(makeObject(value));
+ }
+ }
return true;
}
+QPDFObjectHandle
+QPDF::JSONReactor::makeObject(JSON const& value)
+{
+ QPDFObjectHandle result;
+ std::string str_v;
+ bool bool_v = false;
+ std::smatch m;
+ if (value.isDictionary()) {
+ result = QPDFObjectHandle::newDictionary();
+ object_stack.push_back(result);
+ } else if (value.isArray()) {
+ result = QPDFObjectHandle::newArray();
+ object_stack.push_back(result);
+ } else if (value.isNull()) {
+ result = QPDFObjectHandle::newNull();
+ } else if (value.getBool(bool_v)) {
+ result = QPDFObjectHandle::newBool(bool_v);
+ } else if (value.getNumber(str_v)) {
+ if (QUtil::is_long_long(str_v.c_str())) {
+ result = QPDFObjectHandle::newInteger(
+ QUtil::string_to_ll(str_v.c_str()));
+ } else {
+ result = QPDFObjectHandle::newReal(str_v);
+ }
+ } else if (value.getString(str_v)) {
+ if (std::regex_match(str_v, m, INDIRECT_OBJ_RE)) {
+ result = reserveObject(m[1].str(), m[2].str());
+ } else if (std::regex_match(str_v, m, UNICODE_RE)) {
+ result = QPDFObjectHandle::newUnicodeString(m[1].str());
+ } else if (std::regex_match(str_v, m, BINARY_RE)) {
+ result = QPDFObjectHandle::newString(QUtil::hex_decode(m[1].str()));
+ } else if (std::regex_match(str_v, m, NAME_RE)) {
+ result = QPDFObjectHandle::newName(str_v);
+ } else {
+ QTC::TC("qpdf", "QPDF_json unrecognized string value");
+ error(value.getStart(), "unrecognized string value");
+ result = QPDFObjectHandle::newNull();
+ }
+ }
+ if (!result.isInitialized()) {
+ throw std::logic_error(
+ "JSONReactor::makeObject didn't initialize the object");
+ }
+
+ // QXXXQ include object number in description
+ result.setObjectDescription(
+ &this->pdf,
+ this->filename + " offset " + QUtil::uint_to_string(value.getStart()));
+ return result;
+}
+
void
QPDF::createFromJSON(std::string const& json_file)
{
@@ -225,6 +484,7 @@ QPDF::createFromJSON(std::string const& json_file)
void
QPDF::createFromJSON(std::shared_ptr<InputSource> is)
{
+ processMemoryFile(is->getName().c_str(), JSON_PDF, strlen(JSON_PDF));
importJSON(is, true);
}
@@ -243,10 +503,19 @@ QPDF::updateFromJSON(std::shared_ptr<InputSource> is)
void
QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete)
{
- JSONReactor reactor(*this, must_be_complete);
+ JSONReactor reactor(*this, is->getName(), must_be_complete);
try {
JSON::parse(*is, &reactor);
} catch (std::runtime_error& e) {
throw std::runtime_error(is->getName() + ": " + e.what());
}
+ if (reactor.anyErrors()) {
+ throw std::runtime_error(is->getName() + ": errors found in JSON");
+ }
+ // QXXXQ
+ // std::cout << "trailer:\n" << getTrailer().unparse() << std::endl;
+ // for (auto& oh: getAllObjects()) {
+ // std::cout << oh.unparse() << ":" << std::endl;
+ // std::cout << oh.unparseResolved() << std::endl;
+ // }
}
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index 69bd350b..efb4d723 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -659,3 +659,12 @@ QPDF_json bad pdf version 0
QPDF_json top-level array 0
QPDF_json bad object key 0
QPDF_json trailer stream 0
+QPDF_json missing trailer 0
+QPDF_json missing objects 0
+QPDF_json ignoring in st_ignore 0
+QPDF_json stream dict not dict 0
+QPDF_json unrecognized string value 0
+QPDF_json data datafile both or neither 0
+QPDF_json stream no dict 0
+QPDF_json trailer no value 0
+QPDF_json value stream both or neither 0
diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test
index 39078a87..89d5cd4e 100644
--- a/qpdf/qtest/qpdf-json.test
+++ b/qpdf/qtest/qpdf-json.test
@@ -33,6 +33,9 @@ my @badfiles = (
'stream-dict-not-dict',
'trailer-not-dict',
'trailer-stream',
+ 'missing-trailer',
+ 'missing-objects',
+ 'obj-key-errors',
);
$n_tests += scalar(@badfiles);
diff --git a/qpdf/qtest/qpdf/qjson-bad-json-version1.out b/qpdf/qtest/qpdf/qjson-bad-json-version1.out
index 1c921c2b..fff93d8a 100644
--- a/qpdf/qtest/qpdf/qjson-bad-json-version1.out
+++ b/qpdf/qtest/qpdf/qjson-bad-json-version1.out
@@ -1 +1,2 @@
-qpdf: qjson-bad-json-version1.json: offset 98: only JSON version 2 is supported
+WARNING: qjson-bad-json-version1.json (offset 98): only JSON version 2 is supported
+qpdf: qjson-bad-json-version1.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-bad-json-version2.out b/qpdf/qtest/qpdf/qjson-bad-json-version2.out
index 756df7fe..351fbb5a 100644
--- a/qpdf/qtest/qpdf/qjson-bad-json-version2.out
+++ b/qpdf/qtest/qpdf/qjson-bad-json-version2.out
@@ -1 +1,2 @@
-qpdf: qjson-bad-json-version2.json: offset 98: only JSON version 2 is supported
+WARNING: qjson-bad-json-version2.json (offset 98): only JSON version 2 is supported
+qpdf: qjson-bad-json-version2.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-bad-object-key.out b/qpdf/qtest/qpdf/qjson-bad-object-key.out
index 91edfc8f..915b5261 100644
--- a/qpdf/qtest/qpdf/qjson-bad-object-key.out
+++ b/qpdf/qtest/qpdf/qjson-bad-object-key.out
@@ -1 +1,2 @@
-qpdf: qjson-bad-object-key.json: offset 181: object key should be "trailer" or "obj:n n R"
+WARNING: qjson-bad-object-key.json (offset 181): object key should be "trailer" or "obj:n n R"
+qpdf: qjson-bad-object-key.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 61331957..2e9f4328 100644
--- a/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out
+++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out
@@ -1 +1,2 @@
-qpdf: qjson-bad-pdf-version1.json: offset 119: invalid PDF version (must be x.y)
+WARNING: qjson-bad-pdf-version1.json (offset 119): 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 2ef92df4..39ad8446 100644
--- a/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out
+++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out
@@ -1 +1,2 @@
-qpdf: qjson-bad-pdf-version2.json: offset 119: invalid PDF version (must be x.y)
+WARNING: qjson-bad-pdf-version2.json (offset 119): invalid PDF version (must be x.y)
+qpdf: qjson-bad-pdf-version2.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-missing-objects.json b/qpdf/qtest/qpdf/qjson-missing-objects.json
new file mode 100644
index 00000000..a2b4930b
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-missing-objects.json
@@ -0,0 +1,11 @@
+{
+ "version": 2,
+ "parameters": {
+ "decodelevel": "none"
+ },
+ "qpdf": {
+ "jsonversion": 2,
+ "pdfversion": "1.3",
+ "maxobjectid": 6
+ }
+}
diff --git a/qpdf/qtest/qpdf/qjson-missing-objects.out b/qpdf/qtest/qpdf/qjson-missing-objects.out
new file mode 100644
index 00000000..6b0a6715
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-missing-objects.out
@@ -0,0 +1,2 @@
+WARNING: qjson-missing-objects.json: "qpdf.objects" was not seen
+qpdf: qjson-missing-objects.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-missing-trailer.json b/qpdf/qtest/qpdf/qjson-missing-trailer.json
new file mode 100644
index 00000000..4fcf82aa
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-missing-trailer.json
@@ -0,0 +1,67 @@
+{
+ "version": 2,
+ "parameters": {
+ "decodelevel": "none"
+ },
+ "qpdf": {
+ "jsonversion": 2,
+ "pdfversion": "1.3",
+ "maxobjectid": 6,
+ "objects": {
+ "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",
+ "/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": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
+ "dict": {}
+ }
+ },
+ "obj:5 0 R": {
+ "value": [
+ "/PDF",
+ "/Text"
+ ]
+ },
+ "obj:6 0 R": {
+ "value": {
+ "/BaseFont": "/Helvetica",
+ "/Encoding": "/WinAnsiEncoding",
+ "/Subtype": "/Type1",
+ "/Type": "/Font"
+ }
+ }
+ }
+ }
+}
diff --git a/qpdf/qtest/qpdf/qjson-missing-trailer.out b/qpdf/qtest/qpdf/qjson-missing-trailer.out
new file mode 100644
index 00000000..817a8c05
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-missing-trailer.out
@@ -0,0 +1,2 @@
+WARNING: qjson-missing-trailer.json: "qpdf.objects.trailer" was not seen
+qpdf: qjson-missing-trailer.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-no-json-version.out b/qpdf/qtest/qpdf/qjson-no-json-version.out
index b745bc85..13b76d41 100644
--- a/qpdf/qtest/qpdf/qjson-no-json-version.out
+++ b/qpdf/qtest/qpdf/qjson-no-json-version.out
@@ -1 +1,2 @@
-qpdf: qjson-no-json-version.json: "qpdf.jsonversion" was not seen
+WARNING: qjson-no-json-version.json: "qpdf.jsonversion" was not seen
+qpdf: qjson-no-json-version.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-no-pdf-version.out b/qpdf/qtest/qpdf/qjson-no-pdf-version.out
index cd752750..dc7df6d5 100644
--- a/qpdf/qtest/qpdf/qjson-no-pdf-version.out
+++ b/qpdf/qtest/qpdf/qjson-no-pdf-version.out
@@ -1 +1,2 @@
-qpdf: qjson-no-pdf-version.json: "qpdf.pdfversion" was not seen
+WARNING: qjson-no-pdf-version.json: "qpdf.pdfversion" was not seen
+qpdf: qjson-no-pdf-version.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-no-qpdf-object.out b/qpdf/qtest/qpdf/qjson-no-qpdf-object.out
index 765d1bc5..63099eba 100644
--- a/qpdf/qtest/qpdf/qjson-no-qpdf-object.out
+++ b/qpdf/qtest/qpdf/qjson-no-qpdf-object.out
@@ -1 +1,2 @@
-qpdf: qjson-no-qpdf-object.json: "qpdf" object was not seen
+WARNING: qjson-no-qpdf-object.json: "qpdf" object was not seen
+qpdf: qjson-no-qpdf-object.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-obj-key-errors.json b/qpdf/qtest/qpdf/qjson-obj-key-errors.json
new file mode 100644
index 00000000..4b9beeb7
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-obj-key-errors.json
@@ -0,0 +1,64 @@
+{
+ "version": 2,
+ "parameters": {
+ "decodelevel": "none"
+ },
+ "qpdf": {
+ "jsonversion": 2,
+ "pdfversion": "1.3",
+ "maxobjectid": 6,
+ "objects": {
+ "obj:1 0 R": {
+ "value": {
+ "/Pages": "2 0 R",
+ "/Type": "/Catalog"
+ }
+ },
+ "obj:2 0 R": {
+ "value": {
+ "/Count": 1,
+ "/Kids": [
+ "3 0 R"
+ ],
+ "/Type": "/Pages"
+ },
+ "stream": {
+ "data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
+ "dict": {}
+ }
+ },
+ "obj:3 0 R": {
+ "potato": {
+ "salad": "ignored-so-no-string-error",
+ "nested": [1, 2, {"x": "y"}]
+ }
+ },
+ "obj:4 0 R": {
+ "stream": {
+ "potato": "u:salad"
+ }
+ },
+ "obj:5 0 R": {
+ "stream": {
+ "dict": {"/A": "/B"},
+ "data": "QlQKICAvRjEgMjQgVGYKICA3MiA3MjAgVGQKICAoUG90YXRvKSBUagpFVAo=",
+ "datafile": "abc"
+ }
+ },
+ "obj:6 0 R": {
+ "value": {
+ "/BaseFont": "/Helvetica",
+ "/Encoding": "/WinAnsiEncoding",
+ "/Subtype": "/Type1",
+ "/Type": "/Font"
+ }
+ },
+ "trailer": {
+ "potato": {
+ "/Root": "1 0 R",
+ "/Size": 7
+ }
+ }
+ }
+ }
+}
diff --git a/qpdf/qtest/qpdf/qjson-obj-key-errors.out b/qpdf/qtest/qpdf/qjson-obj-key-errors.out
new file mode 100644
index 00000000..0098bcee
--- /dev/null
+++ b/qpdf/qtest/qpdf/qjson-obj-key-errors.out
@@ -0,0 +1,7 @@
+WARNING: qjson-obj-key-errors.json (obj:2 0 R, offset 302): object must have exactly one of "value" or "stream"
+WARNING: qjson-obj-key-errors.json (obj:3 0 R, offset 600): object must have exactly one of "value" or "stream"
+WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 768): "stream" is missing "dict"
+WARNING: qjson-obj-key-errors.json (obj:4 0 R, offset 768): "stream" must have exactly one of "data" or "datafile"
+WARNING: qjson-obj-key-errors.json (obj:5 0 R, offset 858): "stream" must have exactly one of "data" or "datafile"
+WARNING: qjson-obj-key-errors.json (trailer, offset 1236): "trailer" is missing "value"
+qpdf: qjson-obj-key-errors.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-object-not-dict.out b/qpdf/qtest/qpdf/qjson-object-not-dict.out
index 5ddeb7f2..daa99065 100644
--- a/qpdf/qtest/qpdf/qjson-object-not-dict.out
+++ b/qpdf/qtest/qpdf/qjson-object-not-dict.out
@@ -1 +1,2 @@
-qpdf: qjson-object-not-dict.json: offset 184: "obj:1 0 R" must be a dictionary
+WARNING: qjson-object-not-dict.json (offset 184): "obj:1 0 R" must be a dictionary
+qpdf: qjson-object-not-dict.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-objects-not-dict.out b/qpdf/qtest/qpdf/qjson-objects-not-dict.out
index 7504428c..16e19695 100644
--- a/qpdf/qtest/qpdf/qjson-objects-not-dict.out
+++ b/qpdf/qtest/qpdf/qjson-objects-not-dict.out
@@ -1 +1,3 @@
-qpdf: qjson-objects-not-dict.json: offset 77: "objects" must be a dictionary
+WARNING: qjson-objects-not-dict.json (offset 77): "objects" must be a dictionary
+WARNING: qjson-objects-not-dict.json: "qpdf.objects.trailer" was not seen
+qpdf: qjson-objects-not-dict.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 0341766e..37a125ec 100644
--- a/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out
+++ b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out
@@ -1 +1,5 @@
-qpdf: qjson-stream-dict-not-dict.json: offset 137: "stream.dict" must be a dictionary
+WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 137): "stream.dict" must be a dictionary
+WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 137): unrecognized string value
+WARNING: qjson-stream-dict-not-dict.json (obj:1 0 R, offset 117): "stream" must have exactly one of "data" or "datafile"
+WARNING: qjson-stream-dict-not-dict.json: "qpdf.objects.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 e1f85a29..f6213ea4 100644
--- a/qpdf/qtest/qpdf/qjson-stream-not-dict.out
+++ b/qpdf/qtest/qpdf/qjson-stream-not-dict.out
@@ -1 +1,3 @@
-qpdf: qjson-stream-not-dict.json: offset 118: "stream" must be a dictionary
+WARNING: qjson-stream-not-dict.json (obj:1 0 R, offset 118): "stream" must be a dictionary
+WARNING: qjson-stream-not-dict.json: "qpdf.objects.trailer" was not seen
+qpdf: qjson-stream-not-dict.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-trailer-not-dict.json b/qpdf/qtest/qpdf/qjson-trailer-not-dict.json
index 69782074..32a5efab 100644
--- a/qpdf/qtest/qpdf/qjson-trailer-not-dict.json
+++ b/qpdf/qtest/qpdf/qjson-trailer-not-dict.json
@@ -63,7 +63,7 @@
}
},
"trailer": {
- "value": false,
+ "value": false
}
}
}
diff --git a/qpdf/qtest/qpdf/qjson-trailer-not-dict.out b/qpdf/qtest/qpdf/qjson-trailer-not-dict.out
index bec6c7ca..fcdf1e9b 100644
--- a/qpdf/qtest/qpdf/qjson-trailer-not-dict.out
+++ b/qpdf/qtest/qpdf/qjson-trailer-not-dict.out
@@ -1 +1,2 @@
-qpdf: qjson-trailer-not-dict.json: offset 1326: "trailer.value" must be a dictionary
+WARNING: qjson-trailer-not-dict.json (trailer, offset 1327): "trailer.value" must be a dictionary
+qpdf: qjson-trailer-not-dict.json: errors found in JSON
diff --git a/qpdf/qtest/qpdf/qjson-trailer-stream.json b/qpdf/qtest/qpdf/qjson-trailer-stream.json
index 74891e5b..0ec21248 100644
--- a/qpdf/qtest/qpdf/qjson-trailer-stream.json
+++ b/qpdf/qtest/qpdf/qjson-trailer-stream.json
@@ -63,7 +63,7 @@
}
},
"trailer": {
- "stream": {},
+ "stream": {}
}
}
}
diff --git a/qpdf/qtest/qpdf/qjson-trailer-stream.out b/qpdf/qtest/qpdf/qjson-trailer-stream.out
index 267ecf08..8ab18888 100644
--- a/qpdf/qtest/qpdf/qjson-trailer-stream.out
+++ b/qpdf/qtest/qpdf/qjson-trailer-stream.out
@@ -1 +1,2 @@
-qpdf: qjson-trailer-stream.json: offset 1327: the trailer may not be a stream
+WARNING: qjson-trailer-stream.json (trailer, offset 1327): the trailer may not be a stream
+qpdf: qjson-trailer-stream.json: errors found in JSON