#include // See "HOW TO ADD A COMMAND-LINE ARGUMENT" in README-maintainer. #include #include #include #include #include #include #include #include namespace { class ArgParser { public: ArgParser(QPDFArgParser& ap, std::shared_ptr c_main); void parseOptions(); private: #include void usage(std::string const& message); void initOptionTables(); QPDFArgParser ap; std::shared_ptr c_main; std::shared_ptr c_copy_att; std::shared_ptr c_att; std::shared_ptr c_pages; std::shared_ptr c_uo; std::shared_ptr c_enc; std::vector accumulated_args; std::string user_password; std::string owner_password; bool called_pages_file{false}; bool called_pages_range{false}; bool used_enc_password_args{false}; bool gave_input{false}; bool gave_output{false}; }; } // namespace ArgParser::ArgParser(QPDFArgParser& ap, std::shared_ptr c_main) : ap(ap), c_main(c_main) { initOptionTables(); } #include void ArgParser::initOptionTables() { #include this->ap.addFinalCheck([this]() { c_main->checkConfiguration(); }); // add_help is defined in auto_job_help.hh add_help(this->ap); // Special case: ignore -- at the top level. This undocumented behavior is for backward // compatibility; it was unintentionally the case prior to 10.6, and some users were relying on // it. this->ap.selectMainOptionTable(); this->ap.addBare("--", []() {}); } void ArgParser::argPositional(std::string const& arg) { if (!this->gave_input) { c_main->inputFile(arg); this->gave_input = true; } else if (!this->gave_output) { c_main->outputFile(arg); this->gave_output = true; } else { usage("unknown argument " + arg); } } void ArgParser::argEmpty() { c_main->emptyInput(); this->gave_input = true; } void ArgParser::argReplaceInput() { c_main->replaceInput(); this->gave_output = true; } void ArgParser::argVersion() { auto whoami = this->ap.getProgname(); *QPDFLogger::defaultLogger()->getInfo() << whoami << " version " << QPDF::QPDFVersion() << "\n" << "Run " << whoami << " --copyright to see copyright and license information.\n"; } void ArgParser::argCopyright() { // clang-format off // Make sure the output looks right on an 80-column display. // 1 2 3 4 5 6 7 8 // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 *QPDFLogger::defaultLogger()->getInfo() << this->ap.getProgname() << " version " << QPDF::QPDFVersion() << "\n" << "\n" << "Copyright (c) 2005-2024 Jay Berkenbilt\n" << "QPDF is licensed under the Apache License, Version 2.0 (the \"License\");\n" << "you may not use this file except in compliance with the License.\n" << "You may obtain a copy of the License at\n" << "\n" << " http://www.apache.org/licenses/LICENSE-2.0\n" << "\n" << "Unless required by applicable law or agreed to in writing, software\n" << "distributed under the License is distributed on an \"AS IS\" BASIS,\n" << "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" << "See the License for the specific language governing permissions and\n" << "limitations under the License.\n" << "\n" << "Versions of qpdf prior to version 7 were released under the terms\n" << "of version 2.0 of the Artistic License. At your option, you may\n" << "continue to consider qpdf to be licensed under those terms. Please\n" << "see the manual for additional information.\n"; // clang-format on } void ArgParser::argJsonHelp(std::string const& parameter) { int version = JSON::LATEST; if (!(parameter.empty() || (parameter == "latest"))) { version = QUtil::string_to_int(parameter.c_str()); } if ((version < 1) || (version > JSON::LATEST)) { usage(std::string("unsupported json version ") + parameter); } *QPDFLogger::defaultLogger()->getInfo() << QPDFJob::json_out_schema(version) << "\n"; } void ArgParser::argShowCrypto() { auto crypto = QPDFCryptoProvider::getRegisteredImpls(); std::string default_crypto = QPDFCryptoProvider::getDefaultProvider(); *QPDFLogger::defaultLogger()->getInfo() << default_crypto << "\n"; for (auto const& iter: crypto) { if (iter != default_crypto) { *QPDFLogger::defaultLogger()->getInfo() << iter << "\n"; } } } void ArgParser::argEncrypt() { this->c_enc = c_main->encrypt(0, "", ""); this->accumulated_args.clear(); this->ap.selectOptionTable(O_ENCRYPTION); } void ArgParser::argEncPositional(std::string const& arg) { if (used_enc_password_args) { usage("positional and dashed encryption arguments may not be mixed"); } this->accumulated_args.push_back(arg); if (this->accumulated_args.size() < 3) { return; } user_password = this->accumulated_args.at(0); owner_password = this->accumulated_args.at(1); auto len_str = this->accumulated_args.at(2); this->accumulated_args.clear(); argEncBits(len_str); } void ArgParser::argEncUserPassword(std::string const& arg) { if (!accumulated_args.empty()) { usage("positional and dashed encryption arguments may not be mixed"); } this->used_enc_password_args = true; this->user_password = arg; } void ArgParser::argEncOwnerPassword(std::string const& arg) { if (!accumulated_args.empty()) { usage("positional and dashed encryption arguments may not be mixed"); } this->used_enc_password_args = true; this->owner_password = arg; } void ArgParser::argEncBits(std::string const& arg) { if (!accumulated_args.empty()) { usage("positional and dashed encryption arguments may not be mixed"); } int keylen = 0; if (arg == "40") { keylen = 40; this->ap.selectOptionTable(O_40_BIT_ENCRYPTION); } else if (arg == "128") { keylen = 128; this->ap.selectOptionTable(O_128_BIT_ENCRYPTION); } else if (arg == "256") { keylen = 256; this->ap.selectOptionTable(O_256_BIT_ENCRYPTION); } else { usage("encryption key length must be 40, 128, or 256"); } this->c_enc = c_main->encrypt(keylen, user_password, owner_password); } void ArgParser::argPages() { this->accumulated_args.clear(); this->c_pages = c_main->pages(); this->ap.selectOptionTable(O_PAGES); } void ArgParser::argPagesPositional(std::string const& arg) { if (!called_pages_file) { c_pages->file(arg); called_pages_file = true; return; } if (called_pages_range) { c_pages->file(arg); called_pages_range = false; return; } // This could be a range or a file. Try parsing. try { QUtil::parse_numrange(arg.c_str(), 0); c_pages->range(arg); called_pages_range = true; } catch (std::runtime_error& e1) { // The range is invalid. Let's see if it's a file. if (arg == ".") { // "." means the input file. QTC::TC("qpdf", "QPDFJob pages range omitted with ."); } else if (QUtil::file_can_be_opened(arg.c_str())) { QTC::TC("qpdf", "QPDFJob pages range omitted in middle"); // Yup, it's a file. } else { // Give the range error usage(e1.what()); } c_pages->file(arg); called_pages_range = false; } } void ArgParser::argEndPages() { c_pages->endPages(); c_pages = nullptr; } void ArgParser::argUnderlay() { this->c_uo = c_main->underlay(); this->ap.selectOptionTable(O_UNDERLAY_OVERLAY); } void ArgParser::argOverlay() { this->c_uo = c_main->overlay(); this->ap.selectOptionTable(O_UNDERLAY_OVERLAY); } void ArgParser::argAddAttachment() { this->c_att = c_main->addAttachment(); this->ap.selectOptionTable(O_ATTACHMENT); } void ArgParser::argCopyAttachmentsFrom() { this->c_copy_att = c_main->copyAttachmentsFrom(); this->ap.selectOptionTable(O_COPY_ATTACHMENT); } void ArgParser::argEndEncryption() { c_enc->endEncrypt(); c_enc = nullptr; } void ArgParser::argEnd40BitEncryption() { argEndEncryption(); } void ArgParser::argEnd128BitEncryption() { argEndEncryption(); } void ArgParser::argEnd256BitEncryption() { argEndEncryption(); } void ArgParser::argUOPositional(std::string const& arg) { c_uo->file(arg); } void ArgParser::argEndUnderlayOverlay() { c_uo->endUnderlayOverlay(); c_uo = nullptr; } void ArgParser::argAttPositional(std::string const& arg) { c_att->file(arg); } void ArgParser::argEndAttachment() { c_att->endAddAttachment(); c_att = nullptr; } void ArgParser::argCopyAttPositional(std::string const& arg) { c_copy_att->file(arg); } void ArgParser::argEndCopyAttachment() { c_copy_att->endCopyAttachmentsFrom(); c_copy_att = nullptr; } void ArgParser::argSetPageLabels() { this->ap.selectOptionTable(O_SET_PAGE_LABELS); accumulated_args.clear(); } void ArgParser::argPageLabelsPositional(std::string const& arg) { accumulated_args.push_back(arg); } void ArgParser::argEndSetPageLabels() { c_main->setPageLabels(accumulated_args); accumulated_args.clear(); } void ArgParser::argJobJsonHelp() { *QPDFLogger::defaultLogger()->getInfo() << QPDFJob::job_json_schema(QPDFJob::LATEST_JOB_JSON) << "\n"; } void ArgParser::usage(std::string const& message) { this->ap.usage(message); } void ArgParser::parseOptions() { try { this->ap.parseArgs(); } catch (std::runtime_error& e) { usage(e.what()); } } void QPDFJob::initializeFromArgv(char const* const argv[], char const* progname_env) { if (progname_env == nullptr) { progname_env = "QPDF_EXECUTABLE"; } int argc = 0; for (auto k = argv; *k; ++k) { ++argc; } QPDFArgParser qap(argc, argv, progname_env); setMessagePrefix(qap.getProgname()); ArgParser ap(qap, config()); ap.parseOptions(); }