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