2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-12-22 19:08:59 +00:00
qpdf/libqpdf/QPDFJob_argv.cc
Jay Berkenbilt a0e70b370a Wiring for --set-page-labels: manual (non-bisectable commit)
This commit contains only the manual changes. It is separated for
clarity. This commit would not pass CI because it lacks the automated
changes, which appear in the next commit.
2024-01-05 17:10:32 -05:00

469 lines
12 KiB
C++

#include <qpdf/QPDFJob.hh>
// See "HOW TO ADD A COMMAND-LINE ARGUMENT" in README-maintainer.
#include <cstring>
#include <iostream>
#include <memory>
#include <qpdf/QPDFArgParser.hh>
#include <qpdf/QPDFCryptoProvider.hh>
#include <qpdf/QPDFLogger.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
namespace
{
class ArgParser
{
public:
ArgParser(QPDFArgParser& ap, std::shared_ptr<QPDFJob::Config> c_main);
void parseOptions();
private:
#include <qpdf/auto_job_decl.hh>
void usage(std::string const& message);
void initOptionTables();
QPDFArgParser ap;
std::shared_ptr<QPDFJob::Config> c_main;
std::shared_ptr<QPDFJob::CopyAttConfig> c_copy_att;
std::shared_ptr<QPDFJob::AttConfig> c_att;
std::shared_ptr<QPDFJob::PagesConfig> c_pages;
std::shared_ptr<QPDFJob::UOConfig> c_uo;
std::shared_ptr<QPDFJob::EncConfig> c_enc;
std::vector<std::string> accumulated_args;
std::shared_ptr<char> pages_password{nullptr};
std::string user_password;
std::string owner_password;
bool used_enc_password_args{false};
bool gave_input{false};
bool gave_output{false};
};
} // namespace
ArgParser::ArgParser(QPDFArgParser& ap, std::shared_ptr<QPDFJob::Config> c_main) :
ap(ap),
c_main(c_main)
{
initOptionTables();
}
#include <qpdf/auto_job_help.hh>
void
ArgParser::initOptionTables()
{
#include <qpdf/auto_job_init.hh>
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::argPagesPassword(std::string const& parameter)
{
if (this->pages_password) {
QTC::TC("qpdf", "QPDFJob duplicated pages password");
usage("--password already specified for this file");
}
if (this->accumulated_args.size() != 1) {
QTC::TC("qpdf", "QPDFJob misplaced pages password");
usage("in --pages, --password must immediately follow a file name");
}
this->pages_password = QUtil::make_shared_cstr(parameter);
}
void
ArgParser::argPagesPositional(std::string const& arg)
{
if (arg.empty()) {
if (this->accumulated_args.empty()) {
return;
}
} else {
this->accumulated_args.push_back(arg);
}
std::string file = this->accumulated_args.at(0);
char const* range_p = nullptr;
size_t n_args = this->accumulated_args.size();
if (n_args >= 2) {
// will be copied before accumulated_args is cleared
range_p = this->accumulated_args.at(1).c_str();
}
// See if the user omitted the range entirely, in which case we assume "1-z".
std::string next_file;
if (range_p == nullptr) {
if (arg.empty()) {
// The filename or password was the last argument
QTC::TC("qpdf", "QPDFJob pages range omitted at end", this->pages_password ? 0 : 1);
} else {
// We need to accumulate some more arguments
return;
}
} else {
try {
QUtil::parse_numrange(range_p, 0);
} catch (std::runtime_error& e1) {
// The range is invalid. Let's see if it's a file.
if (strcmp(range_p, ".") == 0) {
// "." means the input file.
QTC::TC("qpdf", "QPDFJob pages range omitted with .");
} else if (QUtil::file_can_be_opened(range_p)) {
QTC::TC("qpdf", "QPDFJob pages range omitted in middle");
// Yup, it's a file.
} else {
// Give the range error
usage(e1.what());
}
next_file = range_p;
range_p = nullptr;
}
}
std::string range(range_p ? range_p : "1-z");
this->c_pages->pageSpec(file, range, this->pages_password.get());
this->accumulated_args.clear();
this->pages_password = nullptr;
if (!next_file.empty()) {
this->accumulated_args.push_back(next_file);
}
}
void
ArgParser::argEndPages()
{
argPagesPositional("");
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();
}