2
1
mirror of https://github.com/qpdf/qpdf.git synced 2025-02-02 11:58:25 +00:00

Parse objects; stream data is not yet handled

This commit is contained in:
Jay Berkenbilt 2022-05-15 17:38:06 -04:00
parent be0ed6ab5e
commit 7e7a9c4379
29 changed files with 562 additions and 73 deletions

9
TODO
View File

@ -54,6 +54,15 @@ Soon: Break ground on "Document-level work"
Output JSON v2 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 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 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 done as a side effect of running --json, the qpdf part of the json may

View File

@ -998,7 +998,7 @@ class QPDF
class JSONReactor: public JSON::Reactor class JSONReactor: public JSON::Reactor
{ {
public: public:
JSONReactor(QPDF&, bool must_be_complete); JSONReactor(QPDF&, std::string const& filename, bool must_be_complete);
virtual ~JSONReactor() = default; virtual ~JSONReactor() = default;
virtual void dictionaryStart() override; virtual void dictionaryStart() override;
virtual void arrayStart() override; virtual void arrayStart() override;
@ -1008,31 +1008,51 @@ class QPDF
dictionaryItem(std::string const& key, JSON const& value) override; dictionaryItem(std::string const& key, JSON const& value) override;
virtual bool arrayItem(JSON const& value) override; virtual bool arrayItem(JSON const& value) override;
bool anyErrors() const;
private: private:
enum state_e { enum state_e {
st_initial, st_initial,
st_top, st_top,
st_ignore,
st_qpdf, st_qpdf,
st_objects_top, st_objects,
st_trailer_top, st_trailer,
st_object_top, st_object_top,
st_stream, st_stream,
st_object, st_object,
st_ignore,
}; };
void containerStart(); void containerStart();
void nestedState(std::string const& key, JSON const& value, state_e); 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; QPDF& pdf;
std::string filename;
bool must_be_complete; bool must_be_complete;
bool errors;
bool parse_error;
bool saw_qpdf; bool saw_qpdf;
bool saw_objects;
bool saw_json_version; bool saw_json_version;
bool saw_pdf_version; bool saw_pdf_version;
bool saw_trailer; bool saw_trailer;
state_e state; state_e state;
state_e next_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<state_e> state_stack;
std::vector<QPDFObjectHandle> object_stack;
std::set<QPDFObjGen> reserved;
}; };
friend class JSONReactor; friend class JSONReactor;
@ -1080,6 +1100,7 @@ class QPDF
void resolveObjectsInStream(int obj_stream_number); void resolveObjectsInStream(int obj_stream_number);
void stopOnError(std::string const& message); void stopOnError(std::string const& message);
QPDFObjectHandle reserveObjectIfNotExists(int objid, int gen); QPDFObjectHandle reserveObjectIfNotExists(int objid, int gen);
QPDFObjectHandle reserveStream(int objid, int gen);
// Calls finish() on the pipeline when done but does not delete it // Calls finish() on the pipeline when done but does not delete it
bool pipeStreamData( bool pipeStreamData(

View File

@ -1431,7 +1431,6 @@ class QPDFObjectHandle
{ {
return QPDFObjectHandle::newIndirect(qpdf, objid, generation); return QPDFObjectHandle::newIndirect(qpdf, objid, generation);
} }
// object must be dictionary object
static QPDFObjectHandle static QPDFObjectHandle
newStream( newStream(
QPDF* qpdf, QPDF* qpdf,

View File

@ -2166,6 +2166,13 @@ QPDF::reserveObjectIfNotExists(int objid, int gen)
return getObjectByID(objid, gen); return getObjectByID(objid, gen);
} }
QPDFObjectHandle
QPDF::reserveStream(int objid, int gen)
{
return QPDFObjectHandle::Factory::newStream(
this, objid, gen, QPDFObjectHandle::newDictionary(), 0, 0);
}
QPDFObjectHandle QPDFObjectHandle
QPDF::getObjectByObjGen(QPDFObjGen const& og) QPDF::getObjectByObjGen(QPDFObjGen const& og)
{ {

View File

@ -1,40 +1,101 @@
#include <qpdf/QPDF.hh> #include <qpdf/QPDF.hh>
#include <qpdf/FileInputSource.hh> #include <qpdf/FileInputSource.hh>
#include <qpdf/QIntC.hh>
#include <qpdf/QTC.hh> #include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh> #include <qpdf/QUtil.hh>
#include <regex> #include <regex>
namespace // This chart shows an example of the state transitions that would
{ // occur in parsing a minimal file.
class JSONExc: public std::runtime_error
{ // | st_initial
public: // { | -> st_top
JSONExc(JSON const& value, std::string const& msg) : // "qpdf": { | -> st_qpdf
std::runtime_error( // "objects": { | -> st_objects
"offset " + QUtil::uint_to_string(value.getStart()) + ": " + // "obj:1 0 R": { | -> st_object_top
msg) // "value": { | -> st_object
{ // "/Pages": "2 0 R", | ...
} // "/Type": "/Catalog" | ...
}; // } | <- st_object_top
} // namespace // }, | <- 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 PDF_VERSION_RE("^\\d+\\.\\d+$");
static std::regex OBJ_KEY_RE("^obj:(\\d+) (\\d+) R$"); 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), pdf(pdf),
filename(filename),
must_be_complete(must_be_complete), must_be_complete(must_be_complete),
errors(false),
parse_error(false),
saw_qpdf(false), saw_qpdf(false),
saw_objects(false),
saw_json_version(false), saw_json_version(false),
saw_pdf_version(false), saw_pdf_version(false),
saw_trailer(false), saw_trailer(false),
state(st_initial), 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); 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 void
QPDF::JSONReactor::containerStart() QPDF::JSONReactor::containerStart()
{ {
@ -46,7 +107,6 @@ void
QPDF::JSONReactor::dictionaryStart() QPDF::JSONReactor::dictionaryStart()
{ {
containerStart(); containerStart();
// QXXXQ
} }
void void
@ -57,7 +117,6 @@ QPDF::JSONReactor::arrayStart()
QTC::TC("qpdf", "QPDF_json top-level array"); QTC::TC("qpdf", "QPDF_json top-level array");
throw std::runtime_error("QPDF JSON must be a dictionary"); throw std::runtime_error("QPDF JSON must be a dictionary");
} }
// QXXXQ
} }
void void
@ -68,23 +127,102 @@ QPDF::JSONReactor::containerEnd(JSON const& value)
if (state == st_initial) { if (state == st_initial) {
if (!this->saw_qpdf) { if (!this->saw_qpdf) {
QTC::TC("qpdf", "QPDF_json missing 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) { } else if (state == st_objects) {
QTC::TC("qpdf", "QPDF_json missing json version"); if (parse_error) {
throw std::runtime_error("\"qpdf.jsonversion\" was not seen"); // 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) { object_stack.clear();
QTC::TC("qpdf", "QPDF_json missing pdf version"); this->cur_object = "";
throw std::runtime_error("\"qpdf.pdfversion\" was not seen"); 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\"");
}
} }
if (must_be_complete && !this->saw_trailer) { } else if ((state == st_stream) || (state == st_object)) {
/// QTC::TC("qpdf", "QPDF_json missing trailer"); if (!parse_error) {
throw std::runtime_error("\"qpdf.objects.trailer\" was not seen"); object_stack.pop_back();
} }
} 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 void
@ -100,16 +238,20 @@ QPDF::JSONReactor::nestedState(
{ {
// Use this method when the next state is for processing a nested // Use this method when the next state is for processing a nested
// dictionary. // dictionary.
if (!value.isDictionary()) { if (value.isDictionary()) {
throw JSONExc(value, "\"" + key + "\" must be a dictionary"); 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 bool
QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value) QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
{ {
if (state == st_ignore) { if (state == st_ignore) {
QTC::TC("qpdf", "QPDF_json ignoring in st_ignore");
// ignore // ignore
} else if (state == st_top) { } else if (state == st_top) {
if (key == "qpdf") { if (key == "qpdf") {
@ -118,6 +260,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
} else { } else {
// Ignore all other fields for forward compatibility. // Ignore all other fields for forward compatibility.
// Don't use nestedState since this can be any type. // Don't use nestedState since this can be any type.
// QXXXQ QTC
next_state = st_ignore; next_state = st_ignore;
} }
} else if (state == st_qpdf) { } else if (state == st_qpdf) {
@ -126,7 +269,7 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
std::string v; std::string v;
if (!(value.getNumber(v) && (v == "2"))) { if (!(value.getNumber(v) && (v == "2"))) {
QTC::TC("qpdf", "QPDF_json bad json version"); 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") { } else if (key == "pdfversion") {
this->saw_pdf_version = true; this->saw_pdf_version = true;
@ -141,81 +284,197 @@ QPDF::JSONReactor::dictionaryItem(std::string const& key, JSON const& value)
} }
if (!version_okay) { if (!version_okay) {
QTC::TC("qpdf", "QPDF_json bad pdf version"); 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") { } else if (key == "objects") {
nestedState(key, value, st_objects_top); this->saw_objects = true;
nestedState(key, value, st_objects);
} else { } else {
// ignore unknown keys for forward compatibility // 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; std::smatch m;
if (key == "trailer") { if (key == "trailer") {
this->saw_trailer = true; this->saw_trailer = true;
nestedState(key, value, st_trailer_top); nestedState(key, value, st_trailer);
// QXXXQ this->cur_object = "trailer";
} else if (std::regex_match(key, m, OBJ_KEY_RE)) { } 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); nestedState(key, value, st_object_top);
// QXXXQ this->cur_object = key;
} else { } else {
QTC::TC("qpdf", "QPDF_json bad object key"); QTC::TC("qpdf", "QPDF_json bad object key");
throw JSONExc( error(
value, "object key should be \"trailer\" or \"obj:n n R\""); 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) { } 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") { if (key == "value") {
// Don't use nestedState since this can have any type. // Don't use nestedState since this can have any type.
this->saw_value = true;
next_state = st_object; next_state = st_object;
// QXXXQ replacement = makeObject(value);
replaceObject(tos, replacement);
} else if (key == "stream") { } else if (key == "stream") {
this->saw_stream = true;
nestedState(key, value, st_stream); 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 { } else {
// Ignore unknown keys for forward compatibility // 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") { if (key == "value") {
this->saw_value = true;
// The trailer must be a dictionary, so we can use nestedState. // The trailer must be a dictionary, so we can use nestedState.
nestedState("trailer.value", value, st_object); nestedState("trailer.value", value, st_object);
// QXXXQ this->pdf.m->trailer = makeObject(value);
} else if (key == "stream") { } 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"); 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 { } else {
// Ignore unknown keys for forward compatibility // Ignore unknown keys for forward compatibility
// QXXXQ QTC
next_state = st_ignore;
} }
} else if (state == st_stream) { } 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 // Since a stream dictionary must be a dictionary, we can
// use nestedState to transition to st_value. // use nestedState to transition to st_value.
nestedState("stream.dict", value, st_object); 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") { } else if (key == "data") {
this->saw_data = true;
// QXXXQ // QXXXQ
} else if (key == "datafile") { } else if (key == "datafile") {
this->saw_datafile = true;
// QXXXQ // QXXXQ
} else { } else {
// Ignore unknown keys for forward compatibility. // Ignore unknown keys for forward compatibility.
// QXXXQ QTC
next_state = st_ignore; next_state = st_ignore;
} }
} else if (state == st_object) { } 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 { } else {
throw std::logic_error( throw std::logic_error(
"QPDF_json: unknown state " + QUtil::int_to_string(state)); "QPDF_json: unknown state " + QUtil::int_to_string(state));
} }
// QXXXQ
return true; return true;
} }
bool bool
QPDF::JSONReactor::arrayItem(JSON const& value) 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; 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 void
QPDF::createFromJSON(std::string const& json_file) QPDF::createFromJSON(std::string const& json_file)
{ {
@ -225,6 +484,7 @@ QPDF::createFromJSON(std::string const& json_file)
void void
QPDF::createFromJSON(std::shared_ptr<InputSource> is) QPDF::createFromJSON(std::shared_ptr<InputSource> is)
{ {
processMemoryFile(is->getName().c_str(), JSON_PDF, strlen(JSON_PDF));
importJSON(is, true); importJSON(is, true);
} }
@ -243,10 +503,19 @@ QPDF::updateFromJSON(std::shared_ptr<InputSource> is)
void void
QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete) 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 { try {
JSON::parse(*is, &reactor); JSON::parse(*is, &reactor);
} catch (std::runtime_error& e) { } catch (std::runtime_error& e) {
throw std::runtime_error(is->getName() + ": " + e.what()); 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;
// }
} }

View File

@ -659,3 +659,12 @@ QPDF_json bad pdf version 0
QPDF_json top-level array 0 QPDF_json top-level array 0
QPDF_json bad object key 0 QPDF_json bad object key 0
QPDF_json trailer stream 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

View File

@ -33,6 +33,9 @@ my @badfiles = (
'stream-dict-not-dict', 'stream-dict-not-dict',
'trailer-not-dict', 'trailer-not-dict',
'trailer-stream', 'trailer-stream',
'missing-trailer',
'missing-objects',
'obj-key-errors',
); );
$n_tests += scalar(@badfiles); $n_tests += scalar(@badfiles);

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,11 @@
{
"version": 2,
"parameters": {
"decodelevel": "none"
},
"qpdf": {
"jsonversion": 2,
"pdfversion": "1.3",
"maxobjectid": 6
}
}

View File

@ -0,0 +1,2 @@
WARNING: qjson-missing-objects.json: "qpdf.objects" was not seen
qpdf: qjson-missing-objects.json: errors found in JSON

View File

@ -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"
}
}
}
}
}

View File

@ -0,0 +1,2 @@
WARNING: qjson-missing-trailer.json: "qpdf.objects.trailer" was not seen
qpdf: qjson-missing-trailer.json: errors found in JSON

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -63,7 +63,7 @@
} }
}, },
"trailer": { "trailer": {
"value": false, "value": false
} }
} }
} }

View File

@ -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

View File

@ -63,7 +63,7 @@
} }
}, },
"trailer": { "trailer": {
"stream": {}, "stream": {}
} }
} }
} }

View File

@ -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