From 1ddf5b4b4bbc3c00c51aba51ff67be47736f6e88 Mon Sep 17 00:00:00 2001 From: Jay Berkenbilt Date: Mon, 3 Jan 2022 12:16:16 -0500 Subject: [PATCH] QPDFJob increment: get rid of exit, handle verbose Remove all calls to exit() from QPDFJob. Handle code that runs in verbose mode to enable it to make use of output streams and message prefix (whoami) from QPDFJob. This removes temporarily duplicated exit code logic and most access to whoami/std::cout outside of QPDFJob proper. --- include/qpdf/QPDFJob.hh | 25 +- libqpdf/QPDFJob.cc | 447 +++++++++--------- qpdf/qpdf.cc | 19 +- .../append-page-content-damaged-check.out | 1 + qpdf/qtest/qpdf/bad-jpeg-check.out | 1 + qpdf/qtest/qpdf/bad-xref-entry-corrected.out | 1 + qpdf/qtest/qpdf/content-stream-errors.out | 1 + qpdf/qtest/qpdf/damaged-stream.out | 1 + qpdf/qtest/qpdf/eof-reading-token.out | 1 + qpdf/qtest/qpdf/indirect-r-arg.out | 1 + qpdf/qtest/qpdf/invalid-id-xref.out | 1 + qpdf/qtest/qpdf/linearization-bounds-1.out | 1 + qpdf/qtest/qpdf/linearization-bounds-2.out | 1 + .../qpdf/linearization-large-vector-alloc.out | 1 + qpdf/qtest/qpdf/name-pound-images.out | 1 + qpdf/qtest/qpdf/no-pages-types.out | 1 + qpdf/qtest/qpdf/obj0-check.out | 1 + qpdf/qtest/qpdf/pages-loop.out | 1 + qpdf/qtest/qpdf/show-unfilterable.out | 3 +- .../qpdf/split-content-stream-errors.out | 1 + qpdf/qtest/qpdf/xref-errors.out | 1 + qpdf/qtest/qpdf/zero-offset.out | 1 + 22 files changed, 288 insertions(+), 224 deletions(-) diff --git a/include/qpdf/QPDFJob.hh b/include/qpdf/QPDFJob.hh index 03b6aa21..89be7c2c 100644 --- a/include/qpdf/QPDFJob.hh +++ b/include/qpdf/QPDFJob.hh @@ -33,6 +33,7 @@ #include #include #include +#include class QPDFWriter; @@ -42,9 +43,23 @@ class QPDFJob QPDF_DLL QPDFJob(); + // Set name that is used to prefix verbose messages, progress + // messages, and other things that the library writes to output + // and error streams on the caller's behalf. Defaults to "qpdf". + QPDF_DLL + void setMessagePrefix(std::string const&); + + // Override streams that errors and output go to. Defaults are + // std::cout and std::cerr. QPDF_DLL void setOutputStreams(std::ostream* out_stream, std::ostream* err_stream); + // If in verbose mode, call the given function, passing in the + // output stream and message prefix. + QPDF_DLL + void doIfVerbose( + std::function fn); + QPDF_DLL void run(); @@ -63,14 +78,13 @@ class QPDFJob QPDF_DLL bool checkIsEncrypted(); - // Return value is bitwise OR of values from qpdf_encryption_status_e QPDF_DLL unsigned long getEncryptionStatus(); - // QXXXQ From here to END-PUBLIC should all be private + // QXXXQ From here to END-PUBLIC should all be private or + // different somehow public: - QPDF_DLL static JSON json_schema(std::set* keys = 0); QPDF_DLL @@ -165,6 +179,10 @@ class QPDFJob void setWriterOptions(QPDF& pdf, QPDFWriter& w); void doSplitPages(QPDF& pdf, bool& warnings); void writeOutfile(QPDF& pdf); + void doJSON(QPDF& pdf); + void doInspection(QPDF& pdf); + void showEncryption(QPDF& pdf); + void doCheck(QPDF& pdf); enum remove_unref_e { re_auto, re_yes, re_no }; @@ -295,6 +313,7 @@ class QPDFJob Members(); Members(Members const&) = delete; + std::string whoami; bool warnings; bool creates_output; std::ostream* out_stream; diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index e5121bdd..b3379812 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -35,8 +35,6 @@ #include // QXXXQ temporary for compilation -static int constexpr EXIT_ERROR = 2; -static int EXIT_WARNING = 3; // may be changed to 0 at runtime static char const* whoami = "qpdf"; // /QXXXQ @@ -82,7 +80,8 @@ namespace class ProgressReporter: public QPDFWriter::ProgressReporter { public: - ProgressReporter(char const* filename) : + ProgressReporter(std::string const& whoami, char const* filename) : + whoami(whoami), filename(filename) { } @@ -92,6 +91,7 @@ namespace virtual void reportProgress(int); private: + std::string whoami; std::string filename; }; } @@ -133,6 +133,7 @@ ProgressReporter::reportProgress(int percentage) QPDFJob::Members::Members() : + whoami("qpdf"), warnings(false), creates_output(false), out_stream(&std::cout), @@ -244,6 +245,12 @@ QPDFJob::QPDFJob() : { } +void +QPDFJob::setMessagePrefix(std::string const& whoami) +{ + this->m->whoami = whoami; +} + void QPDFJob::setOutputStreams(std::ostream* out, std::ostream* err) { @@ -251,6 +258,16 @@ QPDFJob::setOutputStreams(std::ostream* out, std::ostream* err) this->m->err_stream = err ? err : &std::cerr; } +void +QPDFJob::doIfVerbose( + std::function fn) +{ + if (this->verbose && (this->m->out_stream != nullptr)) + { + fn(*(this->m->out_stream), this->m->whoami); + } +} + static void parse_version(std::string const& full_version_string, std::string& version, int& extension_level) { @@ -316,8 +333,10 @@ static std::string show_encryption_method(QPDF::encryption_method_e method) return result; } -static void show_encryption(QPDF& pdf, QPDFJob& o) +void +QPDFJob::showEncryption(QPDF& pdf) { + QPDFJob& o = *this; // QXXXQ // Extract /P from /Encrypt int R = 0; int P = 0; @@ -380,8 +399,10 @@ static void show_encryption(QPDF& pdf, QPDFJob& o) } } -static void do_check(QPDF& pdf, QPDFJob& o, int& exit_code) +void +QPDFJob::doCheck(QPDF& pdf) { + QPDFJob& o = *this; // QXXXQ // Code below may set okay to false but not to true. // We assume okay until we prove otherwise but may // continue to perform additional checks after finding @@ -399,7 +420,7 @@ static void do_check(QPDF& pdf, QPDFJob& o, int& exit_code) << pdf.getExtensionLevel(); } std::cout << std::endl; - show_encryption(pdf, o); + showEncryption(pdf); if (pdf.isLinearized()) { std::cout << "File is linearized\n"; @@ -443,8 +464,9 @@ static void do_check(QPDF& pdf, QPDFJob& o, int& exit_code) catch (QPDFExc& e) { okay = false; - std::cerr << "ERROR: page " << pageno << ": " - << e.what() << std::endl; + *(this->m->err_stream) + << "ERROR: page " << pageno << ": " + << e.what() << std::endl; } } } @@ -453,28 +475,27 @@ static void do_check(QPDF& pdf, QPDFJob& o, int& exit_code) std::cerr << "ERROR: " << e.what() << std::endl; okay = false; } - if (okay) + if (! okay) { - if ((! pdf.getWarnings().empty()) || warnings) - { - exit_code = EXIT_WARNING; - } - else - { - std::cout << "No syntax or stream encoding errors" - << " found; the file may still contain" - << std::endl - << "errors that qpdf cannot detect" - << std::endl; - } + throw std::runtime_error("errors detected"); + } + + if ((! pdf.getWarnings().empty()) || warnings) + { + this->m->warnings = TRUE; } else { - exit_code = EXIT_ERROR; + *(this->m->out_stream) + << "No syntax or stream encoding errors" + << " found; the file may still contain" + << std::endl + << "errors that qpdf cannot detect" + << std::endl; } } -static void do_show_obj(QPDF& pdf, QPDFJob& o, int& exit_code) +static void do_show_obj(QPDF& pdf, QPDFJob& o) { QPDFObjectHandle obj; if (o.show_trailer) @@ -485,6 +506,7 @@ static void do_show_obj(QPDF& pdf, QPDFJob& o, int& exit_code) { obj = pdf.getObjectByID(o.show_obj, o.show_gen); } + bool error = false; if (obj.isStream()) { if (o.show_raw_stream_data || o.show_filtered_stream_data) @@ -494,9 +516,8 @@ static void do_show_obj(QPDF& pdf, QPDFJob& o, int& exit_code) (! obj.pipeStreamData(0, 0, qpdf_dl_all))) { QTC::TC("qpdf", "qpdf unable to filter"); - std::cerr << "Unable to filter stream data." - << std::endl; - exit_code = EXIT_ERROR; + obj.warnIfPossible("unable to filter stream data"); + error = true; } else { @@ -519,6 +540,11 @@ static void do_show_obj(QPDF& pdf, QPDFJob& o, int& exit_code) { std::cout << obj.unparseResolved() << std::endl; } + if (error) + { + throw std::runtime_error( + "unable to get object " + obj.getObjGen().unparse()); + } } static void do_show_pages(QPDF& pdf, QPDFJob& o) @@ -581,29 +607,28 @@ static void do_list_attachments(QPDF& pdf, QPDFJob& o) std::cout << key << " -> " << efoh->getEmbeddedFileStream().getObjGen() << std::endl; - if (o.verbose) - { + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { auto desc = efoh->getDescription(); if (! desc.empty()) { - std::cout << " description: " << desc << std::endl; + cout << " description: " << desc << std::endl; } - std::cout << " preferred name: " << efoh->getFilename() + cout << " preferred name: " << efoh->getFilename() << std::endl; - std::cout << " all names:" << std::endl; + cout << " all names:" << std::endl; for (auto const& i2: efoh->getFilenames()) { - std::cout << " " << i2.first << " -> " << i2.second + cout << " " << i2.first << " -> " << i2.second << std::endl; } - std::cout << " all data streams:" << std::endl; + cout << " all data streams:" << std::endl; for (auto i2: efoh->getEmbeddedFileStreams().ditems()) { - std::cout << " " << i2.first << " -> " + cout << " " << i2.first << " -> " << i2.second.getObjGen() << std::endl; } - } + }); } } else @@ -612,16 +637,14 @@ static void do_list_attachments(QPDF& pdf, QPDFJob& o) } } -static void do_show_attachment(QPDF& pdf, QPDFJob& o, int& exit_code) +static void do_show_attachment(QPDF& pdf, QPDFJob& o) { QPDFEmbeddedFileDocumentHelper efdh(pdf); auto fs = efdh.getEmbeddedFile(o.attachment_to_show); if (! fs) { - std::cerr << whoami << ": attachment " << o.attachment_to_show - << " not found" << std::endl; - exit_code = EXIT_ERROR; - return; + throw std::runtime_error( + "attachment " + o.attachment_to_show + " not found"); } auto efs = fs->getEmbeddedFileStream(); QUtil::binary_stdout(); @@ -1149,8 +1172,9 @@ QPDFJob::json_schema(std::set* keys) bool all_keys = ((keys == 0) || keys->empty()); + // QXXXQ // The list of selectable top-level keys id duplicated in three - // places: json_schema, do_json, and initOptionTable. + // places: json_schema, doJSON, and initOptionTable. if (all_keys || keys->count("objects")) { schema.addDictionaryMember( @@ -1456,8 +1480,10 @@ QPDFJob::json_schema(std::set* keys) return schema; } -static void do_json(QPDF& pdf, QPDFJob& o) +void +QPDFJob::doJSON(QPDF& pdf) { + QPDFJob& o = *this; // QXXXQ JSON j = JSON::makeDictionary(); // This version is updated every time a non-backward-compatible // change is made to the JSON format. Clients of the JSON are to @@ -1486,8 +1512,9 @@ static void do_json(QPDF& pdf, QPDFJob& o) "decodelevel", JSON::makeString(decode_level_str)); bool all_keys = o.json_keys.empty(); + // QXXXQ // The list of selectable top-level keys id duplicated in three - // places: json_schema, do_json, and initOptionTable. + // places: json_schema, doJSON, and initOptionTable. if (all_keys || o.json_keys.count("objects")) { do_json_objects(pdf, o, j); @@ -1527,9 +1554,9 @@ static void do_json(QPDF& pdf, QPDFJob& o) std::list errors; if (! j.checkSchema(schema, errors)) { - std::cerr - << whoami << " didn't create JSON that complies with its own\n\ -rules. Please report this as a bug at\n\ + *(this->m->err_stream) + << "QPDFJob didn't create JSON that complies with its own rules.\n\ +Please report this as a bug at\n\ https://github.com/qpdf/qpdf/issues/new\n\ ideally with the file that caused the error and the output below. Thanks!\n\ \n"; @@ -1543,16 +1570,17 @@ ideally with the file that caused the error and the output below. Thanks!\n\ std::cout << j.unparse() << std::endl; } -static void do_inspection(QPDF& pdf, QPDFJob& o) +void +QPDFJob::doInspection(QPDF& pdf) { - int exit_code = 0; + QPDFJob& o = *this; // QXXXQ if (o.check) { - do_check(pdf, o, exit_code); + doCheck(pdf); } if (o.json) { - do_json(pdf, o); + doJSON(pdf); } if (o.show_npages) { @@ -1562,7 +1590,7 @@ static void do_inspection(QPDF& pdf, QPDFJob& o) } if (o.show_encryption) { - show_encryption(pdf, o); + showEncryption(pdf); } if (o.check_linearization) { @@ -1571,9 +1599,9 @@ static void do_inspection(QPDF& pdf, QPDFJob& o) std::cout << o.infilename << ": no linearization errors" << std::endl; } - else if (exit_code != EXIT_ERROR) + else { - exit_code = EXIT_WARNING; + this->m->warnings = true; } } if (o.show_linearization) @@ -1594,7 +1622,7 @@ static void do_inspection(QPDF& pdf, QPDFJob& o) } if ((o.show_obj > 0) || o.show_trailer) { - do_show_obj(pdf, o, exit_code); + do_show_obj(pdf, o); } if (o.show_pages) { @@ -1606,17 +1634,11 @@ static void do_inspection(QPDF& pdf, QPDFJob& o) } if (! o.attachment_to_show.empty()) { - do_show_attachment(pdf, o, exit_code); + do_show_attachment(pdf, o); } - if ((! pdf.getWarnings().empty()) && (exit_code != EXIT_ERROR)) + if (! pdf.getWarnings().empty()) { - std::cerr << whoami - << ": operation succeeded with warnings" << std::endl; - exit_code = EXIT_WARNING; - } - if (exit_code) - { - exit(exit_code); // QXXXQ + this->m->warnings = true; } } @@ -1637,11 +1659,13 @@ ImageOptimizer::makePipeline(std::string const& description, Pipeline* next) QPDFObjectHandle colorspace_obj = dict.getKey("/ColorSpace"); if (! (w_obj.isNumber() && h_obj.isNumber())) { - if (o.verbose && (! description.empty())) + if (! description.empty()) { - std::cout << whoami << ": " << description - << ": not optimizing because image dictionary" - << " is missing required keys" << std::endl; + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": " << description + << ": not optimizing because image dictionary" + << " is missing required keys" << std::endl; + }); } return result; } @@ -1649,11 +1673,13 @@ ImageOptimizer::makePipeline(std::string const& description, Pipeline* next) if (! (components_obj.isInteger() && (components_obj.getIntValue() == 8))) { QTC::TC("qpdf", "qpdf image optimize bits per component"); - if (o.verbose && (! description.empty())) + if (! description.empty()) { - std::cout << whoami << ": " << description - << ": not optimizing because image has other than" - << " 8 bits per component" << std::endl; + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": " << description + << ": not optimizing because image has other than" + << " 8 bits per component" << std::endl; + }); } return result; } @@ -1700,11 +1726,13 @@ ImageOptimizer::makePipeline(std::string const& description, Pipeline* next) else { QTC::TC("qpdf", "qpdf image optimize colorspace"); - if (o.verbose && (! description.empty())) + if (! description.empty()) { - std::cout << whoami << ": " << description - << ": not optimizing because qpdf can't optimize" - << " images with this colorspace" << std::endl; + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": " << description + << ": not optimizing because qpdf can't optimize" + << " images with this colorspace" << std::endl; + }); } return result; } @@ -1713,12 +1741,14 @@ ImageOptimizer::makePipeline(std::string const& description, Pipeline* next) ((o.oi_min_area > 0) && ((w * h) <= o.oi_min_area))) { QTC::TC("qpdf", "qpdf image optimize too small"); - if (o.verbose && (! description.empty())) + if (! description.empty()) { - std::cout << whoami << ": " << description - << ": not optimizing because image" - << " is smaller than requested minimum dimensions" - << std::endl; + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": " << description + << ": not optimizing because image" + << " is smaller than requested minimum dimensions" + << std::endl; + }); } return result; } @@ -1733,13 +1763,12 @@ ImageOptimizer::evaluate(std::string const& description) if (! image.pipeStreamData(0, 0, qpdf_dl_specialized, true)) { QTC::TC("qpdf", "qpdf image optimize no pipeline"); - if (o.verbose) - { - std::cout << whoami << ": " << description - << ": not optimizing because unable to decode data" - << " or data already uses DCT" - << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": " << description + << ": not optimizing because unable to decode data" + << " or data already uses DCT" + << std::endl; + }); return false; } Pl_Discard d; @@ -1758,21 +1787,19 @@ ImageOptimizer::evaluate(std::string const& description) if (c.getCount() >= orig_length) { QTC::TC("qpdf", "qpdf image optimize no shrink"); - if (o.verbose) - { - std::cout << whoami << ": " << description - << ": not optimizing because DCT compression does not" - << " reduce image size" << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": " << description + << ": not optimizing because DCT compression does not" + << " reduce image size" << std::endl; + }); return false; } - if (o.verbose) - { - std::cout << whoami << ": " << description - << ": optimizing image reduces size from " - << orig_length << " to " << c.getCount() - << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": " << description + << ": optimizing image reduces size from " + << orig_length << " to " << c.getCount() + << std::endl; + }); return true; } @@ -1889,13 +1916,15 @@ static PointerHolder do_process( throw e; } } - if ((! warned) && o.verbose) + if (! warned) { warned = true; - std::cout << whoami << ": supplied password didn't work;" - << " trying other passwords based on interpreting" - << " password with different string encodings" - << std::endl; + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": supplied password didn't work;" + << " trying other passwords based on interpreting" + << " password with different string encodings" + << std::endl; + }); } } // Should not be reachable @@ -2032,10 +2061,9 @@ static void do_under_overlay_for_page( iter != pagenos[pageno].end(); ++iter) { int from_pageno = *iter; - if (o.verbose) - { - std::cout << " " << uo.which << " " << from_pageno << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << " " << uo.which << " " << from_pageno << std::endl; + }); auto from_page = pages.at(QIntC::to_size(from_pageno - 1)); if (0 == fo.count(from_pageno)) { @@ -2116,16 +2144,14 @@ QPDFJob::handleUnderOverlay(QPDF& pdf) QPDFPageDocumentHelper main_pdh(pdf); std::vector main_pages = main_pdh.getAllPages(); size_t main_npages = main_pages.size(); - if (o.verbose) - { - std::cout << whoami << ": processing underlay/overlay" << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": processing underlay/overlay" << std::endl; + }); for (size_t i = 0; i < main_npages; ++i) { - if (o.verbose) - { - std::cout << " page " << 1+i << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << " page " << 1+i << std::endl; + }); do_under_overlay_for_page(pdf, o, o.underlay, underlay_pagenos, i, underlay_fo, upages, main_pages.at(i), true); @@ -2174,12 +2200,11 @@ QPDFJob::addAttachments(QPDF& pdf) } efdh.replaceEmbeddedFile(to_add.key, fs); - if (o.verbose) - { - std::cout << whoami << ": attached " << to_add.path - << " as " << to_add.filename - << " with key " << to_add.key << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": attached " << to_add.path + << " as " << to_add.filename + << " with key " << to_add.key << std::endl; + }); } if (! duplicated_keys.empty()) @@ -2210,11 +2235,10 @@ QPDFJob::copyAttachments(QPDF& pdf) std::vector duplicates; for (auto const& to_copy: o.attachments_to_copy) { - if (o.verbose) - { - std::cout << whoami << ": copying attachments from " - << to_copy.path << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": copying attachments from " + << to_copy.path << std::endl; + }); auto other = processFile( to_copy.path.c_str(), to_copy.password.c_str()); QPDFEmbeddedFileDocumentHelper other_efdh(*other); @@ -2233,11 +2257,11 @@ QPDFJob::copyAttachments(QPDF& pdf) iter.second->getObjectHandle()); efdh.replaceEmbeddedFile( new_key, QPDFFileSpecObjectHelper(new_fs_oh)); - if (o.verbose) - { - std::cout << " " << iter.first << " -> " << new_key - << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, + std::string const& whoami) { + cout << " " << iter.first << " -> " << new_key + << std::endl; + }); } } @@ -2361,11 +2385,11 @@ QPDFJob::handleTransformations(QPDF& pdf) { if (efdh.removeEmbeddedFile(key)) { - if (o.verbose) - { - std::cout << whoami << + o.doIfVerbose([&](std::ostream& cout, + std::string const& whoami) { + cout << whoami << ": removed attachment " << key << std::endl; - } + }); } else { @@ -2407,11 +2431,10 @@ static bool should_remove_unreferenced_resources(QPDF& pdf, QPDFJob& o) std::set resources_seen; // shared resources detection std::set nodes_seen; // loop detection - if (o.verbose) - { - std::cout << whoami << ": " << pdf.getFilename() - << ": checking for shared resources" << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": " << pdf.getFilename() + << ": checking for shared resources" << std::endl; + }); std::list queue; queue.push_back(pdf.getRoot().getKey("/Pages")); @@ -2433,12 +2456,12 @@ static bool should_remove_unreferenced_resources(QPDF& pdf, QPDFJob& o) if (dict.hasKey("/Resources")) { QTC::TC("qpdf", "qpdf found resources in non-leaf"); - if (o.verbose) - { - std::cout << " found resources in non-leaf page node " - << og.getObj() << " " << og.getGen() - << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, + std::string const& whoami) { + cout << " found resources in non-leaf page node " + << og.getObj() << " " << og.getGen() + << std::endl; + }); return true; } int n = kids.getArrayNItems(); @@ -2457,15 +2480,15 @@ static bool should_remove_unreferenced_resources(QPDF& pdf, QPDFJob& o) if (resources_seen.count(resources_og)) { QTC::TC("qpdf", "qpdf found shared resources in leaf"); - if (o.verbose) - { - std::cout << " found shared resources in leaf node " - << og.getObj() << " " << og.getGen() - << ": " - << resources_og.getObj() << " " - << resources_og.getGen() - << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, + std::string const& whoami) { + cout << " found shared resources in leaf node " + << og.getObj() << " " << og.getGen() + << ": " + << resources_og.getObj() << " " + << resources_og.getGen() + << std::endl; + }); return true; } resources_seen.insert(resources_og); @@ -2479,15 +2502,15 @@ static bool should_remove_unreferenced_resources(QPDF& pdf, QPDFJob& o) if (resources_seen.count(xobject_og)) { QTC::TC("qpdf", "qpdf found shared xobject in leaf"); - if (o.verbose) - { - std::cout << " found shared xobject in leaf node " - << og.getObj() << " " << og.getGen() - << ": " - << xobject_og.getObj() << " " - << xobject_og.getGen() - << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, + std::string const& whoami) { + cout << " found shared xobject in leaf node " + << og.getObj() << " " << og.getGen() + << ": " + << xobject_og.getObj() << " " + << xobject_og.getGen() + << std::endl; + }); return true; } resources_seen.insert(xobject_og); @@ -2512,10 +2535,9 @@ static bool should_remove_unreferenced_resources(QPDF& pdf, QPDFJob& o) } } - if (o.verbose) - { - std::cout << whoami << ": no shared resources found" << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": no shared resources found" << std::endl; + }); return false; } @@ -2570,20 +2592,18 @@ static void handle_page_specs( if (filenames.size() > o.keep_files_open_threshold) { QTC::TC("qpdf", "qpdf disable keep files open"); - if (o.verbose) - { - std::cout << whoami << ": selecting --keep-open-files=n" - << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": selecting --keep-open-files=n" + << std::endl; + }); o.keep_files_open = false; } else { - if (o.verbose) - { - std::cout << whoami << ": selecting --keep-open-files=y" - << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": selecting --keep-open-files=y" + << std::endl; + }); o.keep_files_open = true; QTC::TC("qpdf", "qpdf don't disable keep files open"); } @@ -2618,11 +2638,10 @@ static void handle_page_specs( QTC::TC("qpdf", "qpdf pages encryption password"); password = o.encryption_file_password; } - if (o.verbose) - { - std::cout << whoami << ": processing " - << page_spec.filename << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": processing " + << page_spec.filename << std::endl; + }); PointerHolder is; ClosedFileInputSource* cis = 0; if (! o.keep_files_open) @@ -2690,12 +2709,11 @@ static void handle_page_specs( // without changing their object numbers. This enables other // things in the original file, such as outlines, to continue to // work. - if (o.verbose) - { - std::cout << whoami - << ": removing unreferenced pages from primary input" - << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami + << ": removing unreferenced pages from primary input" + << std::endl; + }); QPDFPageDocumentHelper dh(pdf); std::vector orig_pages = dh.getAllPages(); for (std::vector::iterator iter = @@ -2765,11 +2783,10 @@ static void handle_page_specs( { any_page_labels = true; } - if (o.verbose) - { - std::cout << whoami << ": adding pages from " - << page_data.filename << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": adding pages from " + << page_data.filename << std::endl; + }); for (std::vector::iterator pageno_iter = page_data.selected_pages.begin(); pageno_iter != page_data.selected_pages.end(); @@ -3002,15 +3019,15 @@ static void maybe_fix_write_password(int R, QPDFJob& o, std::string& password) if (QUtil::utf8_to_pdf_doc(password, encoded)) { QTC::TC("qpdf", "qpdf auto-encode password"); - if (o.verbose) - { - std::cout + o.doIfVerbose([&](std::ostream& cout, + std::string const& whoami) { + cout << whoami << ": automatically converting Unicode" << " password to single-byte encoding as" << " required for 40-bit or 128-bit" << " encryption" << std::endl; - } + }); password = encoded; } else @@ -3088,9 +3105,8 @@ static void set_encryption_options(QPDF& pdf, QPDFJob& o, QPDFWriter& w) { if (! o.allow_weak_crypto) { - // Do not set exit code to EXIT_WARNING for this case as - // this does not reflect a potential problem with the - // input file. + // Do not set warnings = true for this case as this does + // not reflect a potential problem with the input file. QTC::TC("qpdf", "qpdf weak crypto warning"); std::cerr << whoami @@ -3247,7 +3263,8 @@ QPDFJob::setWriterOptions(QPDF& pdf, QPDFWriter& w) } if (o.progress && o.outfilename) { - w.registerProgressReporter(new ProgressReporter(o.outfilename)); + w.registerProgressReporter( + new ProgressReporter(this->m->whoami, o.outfilename)); } } @@ -3355,18 +3372,15 @@ QPDFJob::doSplitPages(QPDF& pdf, bool& warnings) std::string outfile = before + page_range + after; if (QUtil::same_file(o.infilename, outfile.c_str())) { - std::cerr << whoami - << ": split pages would overwrite input file with " - << outfile << std::endl; - exit(EXIT_ERROR); // QXXXQ + throw std::runtime_error( + "split pages would overwrite input file with " + outfile); } QPDFWriter w(outpdf, outfile.c_str()); setWriterOptions(outpdf, w); w.write(); - if (o.verbose) - { - std::cout << whoami << ": wrote file " << outfile << std::endl; - } + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": wrote file " << outfile << std::endl; + }); if (outpdf.anyWarnings()) { warnings = true; @@ -3399,10 +3413,11 @@ QPDFJob::writeOutfile(QPDF& pdf) setWriterOptions(pdf, w); w.write(); } - if (o.verbose && o.outfilename) + if (o.outfilename) { - std::cout << whoami << ": wrote file " - << o.outfilename << std::endl; + o.doIfVerbose([&](std::ostream& cout, std::string const& whoami) { + cout << whoami << ": wrote file " << o.outfilename << std::endl; + }); } if (o.replace_input) { @@ -3495,7 +3510,7 @@ QPDFJob::run() this->m->creates_output = ((o.outfilename != nullptr) || o.replace_input); if (! this->m->creates_output) { - do_inspection(pdf, o); + doInspection(pdf); } else if (o.split_pages) { diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index b3170b05..a7613af9 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -314,6 +314,7 @@ ArgParser::initOptionTable() this->ap.addBare("show-pages", b(&ArgParser::argShowPages)); this->ap.addBare("with-images", b(&ArgParser::argWithImages)); this->ap.addBare("json", b(&ArgParser::argJson)); + // QXXXQ // The list of selectable top-level keys id duplicated in three // places: json_schema, do_json, and initOptionTable. char const* json_key_choices[] = { @@ -2350,6 +2351,7 @@ ArgParser::doFinalChecks() } if (o.optimize_images && (! o.keep_inline_images)) { + // QXXXQ this is not a check and doesn't belong here o.externalize_inline_images = true; } if (o.check_requires_password && o.check_is_encrypted) @@ -2384,7 +2386,7 @@ ArgParser::doFinalChecks() usage("--split-pages may not be used when" " writing to standard output"); } - if (o.verbose) + if (o.verbose) // QXXXQ { usage("--verbose may not be used when" " writing to standard output"); @@ -2418,6 +2420,7 @@ int realmain(int argc, char* argv[]) // ArgParser must stay in scope for the duration of qpdf's run as // it holds dynamic memory used for argv. QPDFJob j; + j.setMessagePrefix(whoami); ArgParser ap(argc, argv, j); bool errors = false; @@ -2439,9 +2442,17 @@ int realmain(int argc, char* argv[]) { if (! j.suppressWarnings()) { - std::cerr << whoami << ": operation succeeded with warnings;" - << " resulting file may have some problems" - << std::endl; + if (j.createsOutput()) + { + std::cerr << whoami << ": operation succeeded with warnings;" + << " resulting file may have some problems" + << std::endl; + } + else + { + std::cerr << whoami << ": operation succeeded with warnings" + << std::endl; + } } // Still return with warning code even if warnings were // suppressed, so leave warnings == true. diff --git a/qpdf/qtest/qpdf/append-page-content-damaged-check.out b/qpdf/qtest/qpdf/append-page-content-damaged-check.out index b529bc35..14457be1 100644 --- a/qpdf/qtest/qpdf/append-page-content-damaged-check.out +++ b/qpdf/qtest/qpdf/append-page-content-damaged-check.out @@ -5,3 +5,4 @@ checking append-page-content-damaged.pdf PDF Version: 1.3 File is not encrypted File is not linearized +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/bad-jpeg-check.out b/qpdf/qtest/qpdf/bad-jpeg-check.out index 425c495e..555a6e25 100644 --- a/qpdf/qtest/qpdf/bad-jpeg-check.out +++ b/qpdf/qtest/qpdf/bad-jpeg-check.out @@ -4,3 +4,4 @@ File is not encrypted File is not linearized WARNING: bad-jpeg.pdf (offset 735): error decoding stream data for object 6 0: Not a JPEG file: starts with 0x77 0x77 WARNING: bad-jpeg.pdf (offset 735): stream will be re-processed without filtering to avoid data loss +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/bad-xref-entry-corrected.out b/qpdf/qtest/qpdf/bad-xref-entry-corrected.out index 0fbac406..db11ab28 100644 --- a/qpdf/qtest/qpdf/bad-xref-entry-corrected.out +++ b/qpdf/qtest/qpdf/bad-xref-entry-corrected.out @@ -12,3 +12,4 @@ WARNING: bad-xref-entry.pdf: Attempting to reconstruct cross-reference table 5/0: uncompressed; offset = 583 6/0: uncompressed; offset = 629 7/0: uncompressed; offset = 774 +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/content-stream-errors.out b/qpdf/qtest/qpdf/content-stream-errors.out index 07a2b81a..5c51b8c2 100644 --- a/qpdf/qtest/qpdf/content-stream-errors.out +++ b/qpdf/qtest/qpdf/content-stream-errors.out @@ -5,3 +5,4 @@ File is not linearized WARNING: page object 3 0 stream 7 0 (content, offset 52): parse error while reading object WARNING: page object 5 0 stream 15 0 (stream data, offset 117): EOF found while reading inline image WARNING: page object 6 0 stream 19 0 (content, offset 53): parse error while reading object +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/damaged-stream.out b/qpdf/qtest/qpdf/damaged-stream.out index 3ef6df62..fe2e5818 100644 --- a/qpdf/qtest/qpdf/damaged-stream.out +++ b/qpdf/qtest/qpdf/damaged-stream.out @@ -4,3 +4,4 @@ File is not encrypted File is not linearized WARNING: damaged-stream.pdf (offset 426): error decoding stream data for object 5 0: LZWDecoder: bad code received WARNING: damaged-stream.pdf (offset 426): stream will be re-processed without filtering to avoid data loss +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/eof-reading-token.out b/qpdf/qtest/qpdf/eof-reading-token.out index 596907c7..111965ec 100644 --- a/qpdf/qtest/qpdf/eof-reading-token.out +++ b/qpdf/qtest/qpdf/eof-reading-token.out @@ -3,3 +3,4 @@ PDF Version: 1.3 File is not encrypted File is not linearized WARNING: eof-reading-token.pdf object stream 12 (object 13 0, offset 5): EOF while reading token +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/indirect-r-arg.out b/qpdf/qtest/qpdf/indirect-r-arg.out index 0b8be9e3..ab912e1d 100644 --- a/qpdf/qtest/qpdf/indirect-r-arg.out +++ b/qpdf/qtest/qpdf/indirect-r-arg.out @@ -5,3 +5,4 @@ WARNING: indirect-r-arg.pdf (object 1 0, offset 62): expected dictionary key but PDF Version: 1.3 File is not encrypted File is not linearized +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/invalid-id-xref.out b/qpdf/qtest/qpdf/invalid-id-xref.out index 209d5826..c8e7eefc 100644 --- a/qpdf/qtest/qpdf/invalid-id-xref.out +++ b/qpdf/qtest/qpdf/invalid-id-xref.out @@ -15,3 +15,4 @@ modify annotations: allowed modify other: not allowed modify anything: not allowed File is not linearized +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/linearization-bounds-1.out b/qpdf/qtest/qpdf/linearization-bounds-1.out index 3e28d28a..ff81a903 100644 --- a/qpdf/qtest/qpdf/linearization-bounds-1.out +++ b/qpdf/qtest/qpdf/linearization-bounds-1.out @@ -6,3 +6,4 @@ WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, off WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 1183): attempting to recover stream length WARNING: linearization-bounds-1.pdf (linearization hint stream: object 62 0, offset 1183): recovered stream length: 106 WARNING: error encountered while checking linearization data: linearization-bounds-1.pdf (linearization hint table, offset 1183): /S (shared object) offset is out of bounds +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/linearization-bounds-2.out b/qpdf/qtest/qpdf/linearization-bounds-2.out index a558dd42..b7386ab3 100644 --- a/qpdf/qtest/qpdf/linearization-bounds-2.out +++ b/qpdf/qtest/qpdf/linearization-bounds-2.out @@ -6,3 +6,4 @@ WARNING: linearization-bounds-2.pdf (linearization hint stream: object 62 0, off WARNING: linearization-bounds-2.pdf (linearization hint stream: object 62 0, offset 1183): attempting to recover stream length WARNING: linearization-bounds-2.pdf (linearization hint stream: object 62 0, offset 1183): recovered stream length: 106 WARNING: error encountered while checking linearization data: linearization-bounds-2.pdf (linearization hint table, offset 1183): /S (shared object) offset is out of bounds +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/linearization-large-vector-alloc.out b/qpdf/qtest/qpdf/linearization-large-vector-alloc.out index a30f3e06..66ee998c 100644 --- a/qpdf/qtest/qpdf/linearization-large-vector-alloc.out +++ b/qpdf/qtest/qpdf/linearization-large-vector-alloc.out @@ -6,3 +6,4 @@ WARNING: linearization-large-vector-alloc.pdf (linearization hint stream: object WARNING: linearization-large-vector-alloc.pdf (linearization hint stream: object 62 0, offset 1183): attempting to recover stream length WARNING: linearization-large-vector-alloc.pdf (linearization hint stream: object 62 0, offset 1183): recovered stream length: 106 WARNING: error encountered while checking linearization data: overflow reading bit stream: wanted = 12556; available = 968 +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/name-pound-images.out b/qpdf/qtest/qpdf/name-pound-images.out index 4ceadc35..065e02a7 100644 --- a/qpdf/qtest/qpdf/name-pound-images.out +++ b/qpdf/qtest/qpdf/name-pound-images.out @@ -6,3 +6,4 @@ WARNING: name-pound-images.pdf (object 3 0, offset 471): name with stray # will WARNING: name-pound-images.pdf (object 3 0, offset 508): name with stray # will not work with PDF >= 1.2 WARNING: page object 3 0 stream 4 0 (content, offset 59): name with stray # will not work with PDF >= 1.2 WARNING: page object 3 0 stream 4 0 (content, offset 131): name with stray # will not work with PDF >= 1.2 +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/no-pages-types.out b/qpdf/qtest/qpdf/no-pages-types.out index 28c172a7..1221b68f 100644 --- a/qpdf/qtest/qpdf/no-pages-types.out +++ b/qpdf/qtest/qpdf/no-pages-types.out @@ -4,3 +4,4 @@ File is not encrypted File is not linearized WARNING: no-pages-types.pdf (page tree node, offset 307): /Type key should be /Page but is not; overriding WARNING: no-pages-types.pdf (page tree node, offset 307): /Type key should be /Pages but is not; overriding +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/obj0-check.out b/qpdf/qtest/qpdf/obj0-check.out index 64cb8d15..d4a33eb6 100644 --- a/qpdf/qtest/qpdf/obj0-check.out +++ b/qpdf/qtest/qpdf/obj0-check.out @@ -5,3 +5,4 @@ WARNING: obj0.pdf: Attempting to reconstruct cross-reference table PDF Version: 1.3 File is not encrypted File is not linearized +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/pages-loop.out b/qpdf/qtest/qpdf/pages-loop.out index a84ecb36..dddfc649 100644 --- a/qpdf/qtest/qpdf/pages-loop.out +++ b/qpdf/qtest/qpdf/pages-loop.out @@ -3,3 +3,4 @@ PDF Version: 1.3 File is not encrypted File is not linearized ERROR: pages-loop.pdf (object 3 0): Loop detected in /Pages structure (getAllPages) +qpdf: errors detected diff --git a/qpdf/qtest/qpdf/show-unfilterable.out b/qpdf/qtest/qpdf/show-unfilterable.out index 464ca2c3..ba802d0d 100644 --- a/qpdf/qtest/qpdf/show-unfilterable.out +++ b/qpdf/qtest/qpdf/show-unfilterable.out @@ -1 +1,2 @@ -Unable to filter stream data. +WARNING: unfilterable.pdf, stream object 4 0: unable to filter stream data +qpdf: unable to get object 4,0 diff --git a/qpdf/qtest/qpdf/split-content-stream-errors.out b/qpdf/qtest/qpdf/split-content-stream-errors.out index de5fee75..1eebc495 100644 --- a/qpdf/qtest/qpdf/split-content-stream-errors.out +++ b/qpdf/qtest/qpdf/split-content-stream-errors.out @@ -7,3 +7,4 @@ WARNING: split-content-stream-errors.pdf (offset 557): stream will be re-process WARNING: page object 3 0 (item index 0 (from 0)): ignoring non-stream in an array of streams WARNING: split-content-stream-errors.pdf (offset 557): error decoding stream data for object 6 0: LZWDecoder: bad code received ERROR: page 1: content stream (content stream object 6 0): errors while decoding content stream +qpdf: errors detected diff --git a/qpdf/qtest/qpdf/xref-errors.out b/qpdf/qtest/qpdf/xref-errors.out index 27c97736..66420c35 100644 --- a/qpdf/qtest/qpdf/xref-errors.out +++ b/qpdf/qtest/qpdf/xref-errors.out @@ -13,3 +13,4 @@ File is not linearized 4/0: uncompressed; offset = 307 5/0: uncompressed; offset = 403 6/0: uncompressed; offset = 438 +qpdf: operation succeeded with warnings diff --git a/qpdf/qtest/qpdf/zero-offset.out b/qpdf/qtest/qpdf/zero-offset.out index f789b165..67c99733 100644 --- a/qpdf/qtest/qpdf/zero-offset.out +++ b/qpdf/qtest/qpdf/zero-offset.out @@ -3,3 +3,4 @@ PDF Version: 1.3 File is not encrypted File is not linearized WARNING: zero-offset.pdf (object 6 0): object has offset 0 +qpdf: operation succeeded with warnings