diff options
37 files changed, 1053 insertions, 8 deletions
@@ -54,6 +54,13 @@ Soon: Break ground on "Document-level work" Output JSON v2 ============== +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 +contain object numbers that aren't there. Maybe we need to indicate +whether pages tree repair has been done in the json, but this would +have to be known early in parsing, which is a problem. + General things to remember: * Make sure all the information from --check and other informational diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh index 61efa4ed..2e24b261 100644 --- a/include/qpdf/QPDF.hh +++ b/include/qpdf/QPDF.hh @@ -998,6 +998,7 @@ class QPDF class JSONReactor: public JSON::Reactor { public: + JSONReactor(QPDF&, bool must_be_complete); virtual ~JSONReactor() = default; virtual void dictionaryStart() override; virtual void arrayStart() override; @@ -1006,6 +1007,32 @@ class QPDF virtual bool dictionaryItem(std::string const& key, JSON const& value) override; virtual bool arrayItem(JSON const& value) override; + + private: + enum state_e { + st_initial, + st_top, + st_ignore, + st_qpdf, + st_objects_top, + st_trailer_top, + st_object_top, + st_stream, + st_object, + }; + + void containerStart(); + void nestedState(std::string const& key, JSON const& value, state_e); + + QPDF& pdf; + bool must_be_complete; + bool saw_qpdf; + bool saw_json_version; + bool saw_pdf_version; + bool saw_trailer; + state_e state; + state_e next_state; + std::vector<state_e> state_stack; }; friend class JSONReactor; diff --git a/libqpdf/QPDF_json.cc b/libqpdf/QPDF_json.cc index 316c9935..2f74a673 100644 --- a/libqpdf/QPDF_json.cc +++ b/libqpdf/QPDF_json.cc @@ -1,42 +1,218 @@ #include <qpdf/QPDF.hh> #include <qpdf/FileInputSource.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 + +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() { - // QXXXXQ + containerStart(); + // QXXXQ } void QPDF::JSONReactor::arrayStart() { - // QXXXXQ + 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) { - // QXXXXQ + 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() { - // QXXXXQ + 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) { - // QXXXXQ + 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) { - // QXXXXQ + // QXXXQ return true; } @@ -65,7 +241,12 @@ QPDF::updateFromJSON(std::shared_ptr<InputSource> is) } void -QPDF::importJSON(std::shared_ptr<InputSource>, bool must_be_complete) +QPDF::importJSON(std::shared_ptr<InputSource> is, bool must_be_complete) { - // QXXXQ + JSONReactor reactor(*this, must_be_complete); + try { + JSON::parse(*is, &reactor); + } catch (std::runtime_error& e) { + throw std::runtime_error(is->getName() + ": " + e.what()); + } } diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov index 4225fc64..69bd350b 100644 --- a/qpdf/qpdf.testcov +++ b/qpdf/qpdf.testcov @@ -650,3 +650,12 @@ QPDFJob json encrypt duplicate key length 0 QPDFJob json encrypt missing password 0 QPDFJob json pages no file 0 qpdf-c called qpdf_empty_pdf 0 +QPDF_json missing qpdf 0 +QPDF_json missing json version 0 +QPDF_json missing pdf version 0 +QPDF_json top-level scalar 0 +QPDF_json bad json version 0 +QPDF_json bad pdf version 0 +QPDF_json top-level array 0 +QPDF_json bad object key 0 +QPDF_json trailer stream 0 diff --git a/qpdf/qtest/qpdf-json.test b/qpdf/qtest/qpdf-json.test new file mode 100644 index 00000000..39078a87 --- /dev/null +++ b/qpdf/qtest/qpdf-json.test @@ -0,0 +1,50 @@ +#!/usr/bin/env perl +require 5.008; +use warnings; +use strict; + +unshift(@INC, '.'); +require qpdf_test_helpers; + +chdir("qpdf") or die "chdir testdir failed: $!\n"; + +require TestDriver; + +cleanup(); + +my $td = new TestDriver('qpdf-json'); + +my $n_tests = 0; + +my @badfiles = ( + 'no-qpdf-object', + 'no-json-version', + 'no-pdf-version', + 'top-level-scalar', + 'bad-json-version1', + 'bad-json-version2', + 'bad-pdf-version1', + 'bad-pdf-version2', + 'top-level-array', + 'objects-not-dict', + 'bad-object-key', + 'object-not-dict', + 'stream-not-dict', + 'stream-dict-not-dict', + 'trailer-not-dict', + 'trailer-stream', + ); + +$n_tests += scalar(@badfiles); + +foreach my $f (@badfiles) +{ + $td->runtest("bad: $f", + {$td->COMMAND => + "qpdf --create-from-json=qjson-$f.json a.pdf"}, + {$td->FILE => "qjson-$f.out", $td->EXIT_STATUS => 2}, + $td->NORMALIZE_NEWLINES); +} + +cleanup(); +$td->report($n_tests); diff --git a/qpdf/qtest/qpdf/qjson-bad-json-version1.json b/qpdf/qtest/qpdf/qjson-bad-json-version1.json new file mode 100644 index 00000000..78591a42 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-json-version1.json @@ -0,0 +1,73 @@ +{ + "version": 2, + "parameters": { + "decodelevel": "none" + }, + "qpdf": { + "jsonversion": 16059, + "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" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-bad-json-version1.out b/qpdf/qtest/qpdf/qjson-bad-json-version1.out new file mode 100644 index 00000000..1c921c2b --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-json-version1.out @@ -0,0 +1 @@ +qpdf: qjson-bad-json-version1.json: offset 98: only JSON version 2 is supported diff --git a/qpdf/qtest/qpdf/qjson-bad-json-version2.json b/qpdf/qtest/qpdf/qjson-bad-json-version2.json new file mode 100644 index 00000000..20843caf --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-json-version2.json @@ -0,0 +1,73 @@ +{ + "version": 2, + "parameters": { + "decodelevel": "none" + }, + "qpdf": { + "jsonversion": "potato", + "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" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-bad-json-version2.out b/qpdf/qtest/qpdf/qjson-bad-json-version2.out new file mode 100644 index 00000000..756df7fe --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-json-version2.out @@ -0,0 +1 @@ +qpdf: qjson-bad-json-version2.json: offset 98: only JSON version 2 is supported diff --git a/qpdf/qtest/qpdf/qjson-bad-object-key.json b/qpdf/qtest/qpdf/qjson-bad-object-key.json new file mode 100644 index 00000000..77441846 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-object-key.json @@ -0,0 +1,75 @@ +{ + "version": 2, + "parameters": { + "decodelevel": "none" + }, + "qpdf": { + "jsonversion": 2, + "pdfversion": "1.3", + "maxobjectid": 6, + "objects": { + "potato": { + }, + "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" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-bad-object-key.out b/qpdf/qtest/qpdf/qjson-bad-object-key.out new file mode 100644 index 00000000..91edfc8f --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-object-key.out @@ -0,0 +1 @@ +qpdf: qjson-bad-object-key.json: offset 181: object key should be "trailer" or "obj:n n R" diff --git a/qpdf/qtest/qpdf/qjson-bad-pdf-version1.json b/qpdf/qtest/qpdf/qjson-bad-pdf-version1.json new file mode 100644 index 00000000..eccaeeca --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version1.json @@ -0,0 +1,73 @@ +{ + "version": 2, + "parameters": { + "decodelevel": "none" + }, + "qpdf": { + "jsonversion": 2, + "pdfversion": "potato", + "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" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out b/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out new file mode 100644 index 00000000..61331957 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version1.out @@ -0,0 +1 @@ +qpdf: qjson-bad-pdf-version1.json: offset 119: invalid PDF version (must be x.y) diff --git a/qpdf/qtest/qpdf/qjson-bad-pdf-version2.json b/qpdf/qtest/qpdf/qjson-bad-pdf-version2.json new file mode 100644 index 00000000..04443eea --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version2.json @@ -0,0 +1,73 @@ +{ + "version": 2, + "parameters": { + "decodelevel": "none" + }, + "qpdf": { + "jsonversion": 2, + "pdfversion": [], + "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" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out b/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out new file mode 100644 index 00000000..2ef92df4 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-bad-pdf-version2.out @@ -0,0 +1 @@ +qpdf: qjson-bad-pdf-version2.json: offset 119: invalid PDF version (must be x.y) diff --git a/qpdf/qtest/qpdf/qjson-no-json-version.json b/qpdf/qtest/qpdf/qjson-no-json-version.json new file mode 100644 index 00000000..72f223ad --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-no-json-version.json @@ -0,0 +1,72 @@ +{ + "version": 2, + "parameters": { + "decodelevel": "none" + }, + "qpdf": { + "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" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-no-json-version.out b/qpdf/qtest/qpdf/qjson-no-json-version.out new file mode 100644 index 00000000..b745bc85 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-no-json-version.out @@ -0,0 +1 @@ +qpdf: qjson-no-json-version.json: "qpdf.jsonversion" was not seen diff --git a/qpdf/qtest/qpdf/qjson-no-pdf-version.json b/qpdf/qtest/qpdf/qjson-no-pdf-version.json new file mode 100644 index 00000000..39f21be2 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-no-pdf-version.json @@ -0,0 +1,72 @@ +{ + "version": 2, + "parameters": { + "decodelevel": "none" + }, + "qpdf": { + "jsonversion": 2, + "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" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-no-pdf-version.out b/qpdf/qtest/qpdf/qjson-no-pdf-version.out new file mode 100644 index 00000000..cd752750 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-no-pdf-version.out @@ -0,0 +1 @@ +qpdf: qjson-no-pdf-version.json: "qpdf.pdfversion" was not seen diff --git a/qpdf/qtest/qpdf/qjson-no-qpdf-object.json b/qpdf/qtest/qpdf/qjson-no-qpdf-object.json new file mode 100644 index 00000000..994a97e8 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-no-qpdf-object.json @@ -0,0 +1,3 @@ +{ + "potato": "salad" +} diff --git a/qpdf/qtest/qpdf/qjson-no-qpdf-object.out b/qpdf/qtest/qpdf/qjson-no-qpdf-object.out new file mode 100644 index 00000000..765d1bc5 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-no-qpdf-object.out @@ -0,0 +1 @@ +qpdf: qjson-no-qpdf-object.json: "qpdf" object was not seen diff --git a/qpdf/qtest/qpdf/qjson-object-not-dict.json b/qpdf/qtest/qpdf/qjson-object-not-dict.json new file mode 100644 index 00000000..c0831c74 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-object-not-dict.json @@ -0,0 +1,68 @@ +{ + "version": 2, + "parameters": { + "decodelevel": "none" + }, + "qpdf": { + "jsonversion": 2, + "pdfversion": "1.3", + "maxobjectid": 6, + "objects": { + "obj:1 0 R": "potato", + "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" + } + }, + "trailer": { + "value": { + "/Root": "1 0 R", + "/Size": 7 + } + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-object-not-dict.out b/qpdf/qtest/qpdf/qjson-object-not-dict.out new file mode 100644 index 00000000..5ddeb7f2 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-object-not-dict.out @@ -0,0 +1 @@ +qpdf: qjson-object-not-dict.json: offset 184: "obj:1 0 R" must be a dictionary diff --git a/qpdf/qtest/qpdf/qjson-objects-not-dict.json b/qpdf/qtest/qpdf/qjson-objects-not-dict.json new file mode 100644 index 00000000..0e16294a --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-objects-not-dict.json @@ -0,0 +1,7 @@ +{ + "qpdf": { + "jsonversion": 2, + "pdfversion": "1.7", + "objects": false + } +} diff --git a/qpdf/qtest/qpdf/qjson-objects-not-dict.out b/qpdf/qtest/qpdf/qjson-objects-not-dict.out new file mode 100644 index 00000000..7504428c --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-objects-not-dict.out @@ -0,0 +1 @@ +qpdf: qjson-objects-not-dict.json: offset 77: "objects" must be a dictionary diff --git a/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.json b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.json new file mode 100644 index 00000000..19bd08b1 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.json @@ -0,0 +1,13 @@ +{ + "qpdf": { + "jsonversion": 2, + "pdfversion": "1.7", + "objects": { + "obj:1 0 R": { + "stream": { + "dict": "quack" + } + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out new file mode 100644 index 00000000..0341766e --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-stream-dict-not-dict.out @@ -0,0 +1 @@ +qpdf: qjson-stream-dict-not-dict.json: offset 137: "stream.dict" must be a dictionary diff --git a/qpdf/qtest/qpdf/qjson-stream-not-dict.json b/qpdf/qtest/qpdf/qjson-stream-not-dict.json new file mode 100644 index 00000000..cef86c95 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-stream-not-dict.json @@ -0,0 +1,11 @@ +{ + "qpdf": { + "jsonversion": 2, + "pdfversion": "1.7", + "objects": { + "obj:1 0 R": { + "stream": 3 + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-stream-not-dict.out b/qpdf/qtest/qpdf/qjson-stream-not-dict.out new file mode 100644 index 00000000..e1f85a29 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-stream-not-dict.out @@ -0,0 +1 @@ +qpdf: qjson-stream-not-dict.json: offset 118: "stream" must be a dictionary diff --git a/qpdf/qtest/qpdf/qjson-top-level-array.json b/qpdf/qtest/qpdf/qjson-top-level-array.json new file mode 100644 index 00000000..2e2c15f8 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-top-level-array.json @@ -0,0 +1 @@ +["potato"] diff --git a/qpdf/qtest/qpdf/qjson-top-level-array.out b/qpdf/qtest/qpdf/qjson-top-level-array.out new file mode 100644 index 00000000..b8a90532 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-top-level-array.out @@ -0,0 +1 @@ +qpdf: qjson-top-level-array.json: QPDF JSON must be a dictionary diff --git a/qpdf/qtest/qpdf/qjson-top-level-scalar.json b/qpdf/qtest/qpdf/qjson-top-level-scalar.json new file mode 100644 index 00000000..a9d93e43 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-top-level-scalar.json @@ -0,0 +1 @@ +"potato" diff --git a/qpdf/qtest/qpdf/qjson-top-level-scalar.out b/qpdf/qtest/qpdf/qjson-top-level-scalar.out new file mode 100644 index 00000000..75649cf1 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-top-level-scalar.out @@ -0,0 +1 @@ +qpdf: qjson-top-level-scalar.json: QPDF JSON must be a dictionary diff --git a/qpdf/qtest/qpdf/qjson-trailer-not-dict.json b/qpdf/qtest/qpdf/qjson-trailer-not-dict.json new file mode 100644 index 00000000..69782074 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-trailer-not-dict.json @@ -0,0 +1,70 @@ +{ + "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" + } + }, + "trailer": { + "value": false, + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-trailer-not-dict.out b/qpdf/qtest/qpdf/qjson-trailer-not-dict.out new file mode 100644 index 00000000..bec6c7ca --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-trailer-not-dict.out @@ -0,0 +1 @@ +qpdf: qjson-trailer-not-dict.json: offset 1326: "trailer.value" must be a dictionary diff --git a/qpdf/qtest/qpdf/qjson-trailer-stream.json b/qpdf/qtest/qpdf/qjson-trailer-stream.json new file mode 100644 index 00000000..74891e5b --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-trailer-stream.json @@ -0,0 +1,70 @@ +{ + "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" + } + }, + "trailer": { + "stream": {}, + } + } + } +} diff --git a/qpdf/qtest/qpdf/qjson-trailer-stream.out b/qpdf/qtest/qpdf/qjson-trailer-stream.out new file mode 100644 index 00000000..267ecf08 --- /dev/null +++ b/qpdf/qtest/qpdf/qjson-trailer-stream.out @@ -0,0 +1 @@ +qpdf: qjson-trailer-stream.json: offset 1327: the trailer may not be a stream |