diff options
-rw-r--r-- | include/qpdf/QPDFArgParser.hh | 5 | ||||
-rw-r--r-- | include/qpdf/QPDFJob.hh | 29 | ||||
-rw-r--r-- | libqpdf/QPDFArgParser.cc | 6 | ||||
-rw-r--r-- | libqpdf/QPDFJob.cc | 7 | ||||
-rw-r--r-- | libqpdf/QPDFJob_argv.cc | 2394 | ||||
-rw-r--r-- | libqpdf/build.mk | 1 | ||||
-rw-r--r-- | qpdf/qpdf.cc | 2403 | ||||
-rw-r--r-- | qpdf/qtest/qpdf/split-pages-stdout.out | 1 |
8 files changed, 2462 insertions, 2384 deletions
diff --git a/include/qpdf/QPDFArgParser.hh b/include/qpdf/QPDFArgParser.hh index 2c46c4e0..e5b2c490 100644 --- a/include/qpdf/QPDFArgParser.hh +++ b/include/qpdf/QPDFArgParser.hh @@ -73,6 +73,11 @@ class QPDFArgParser QPDF_DLL void parseArgs(); + // Return the program name as the last path element of the program + // executable. + QPDF_DLL + std::string getProgname(); + // Methods for registering arguments. QPDFArgParser starts off // with the main option table selected. You can add handlers for // arguments in the current option table, and you can select which diff --git a/include/qpdf/QPDFJob.hh b/include/qpdf/QPDFJob.hh index 660f691f..f2e3a019 100644 --- a/include/qpdf/QPDFJob.hh +++ b/include/qpdf/QPDFJob.hh @@ -27,6 +27,7 @@ #include <qpdf/PointerHolder.hh> #include <qpdf/QPDF.hh> #include <qpdf/QPDFPageObjectHelper.hh> +#include <qpdf/QPDFArgParser.hh> #include <string> #include <list> @@ -44,6 +45,29 @@ class QPDFJob QPDF_DLL QPDFJob(); + // Initialize a QPDFJob object from argv. The progname_env + // argument is the name of an environment variable which, if set, + // overrides the name of the executable for purposes of generating + // the --completion options. See QPDFArgParser for details. If a + // null pointer is passed in, the default value of + // "QPDF_EXECUTABLE" is used. This is used by the QPDF cli, which + // just initializes a QPDFJob from argv, calls run(), and handles + // errors and exit status issues. You can perform much of the cli + // functionality programmatically in this way rather than using + // the regular API. This is exposed in the C API, which makes it + // easier to get certain high-level qpdf functionality from other + // languages. If there are any command-line errors, this method + // will throw QPDFArgParser::Usage which is derived from + // std::runtime_error. Other exceptions may be thrown in some + // cases. Note that argc, and argv should be UTF-8 encoded. If you + // are calling this from a Windows Unicode-aware main (wmain), see + // QUtil::call_main_from_wmain for information about converting + // arguments to UTF-8. This method will mutate arguments that are + // passed to it. + QPDF_DLL + void initializeFromArgv(int argc, char* argv[], + char const* progname_env = nullptr); + // 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". @@ -74,6 +98,9 @@ class QPDFJob bool suppressWarnings(); QPDF_DLL + bool warningsExitZero(); + + QPDF_DLL bool checkRequiresPassword(); QPDF_DLL @@ -180,6 +207,7 @@ class QPDFJob bool verbose; bool progress; bool suppress_warnings; + bool warnings_exit_zero; bool copy_encryption; char const* encryption_file; char const* encryption_file_password; @@ -367,6 +395,7 @@ class QPDFJob std::ostream* cout; std::ostream* cerr; unsigned long encryption_status; + PointerHolder<QPDFArgParser> ap; }; PointerHolder<Members> m; }; diff --git a/libqpdf/QPDFArgParser.cc b/libqpdf/QPDFArgParser.cc index a1e9b785..33dbf2e0 100644 --- a/libqpdf/QPDFArgParser.cc +++ b/libqpdf/QPDFArgParser.cc @@ -662,6 +662,12 @@ QPDFArgParser::parseArgs() } } +std::string +QPDFArgParser::getProgname() +{ + return this->m->whoami; +} + void QPDFArgParser::doFinalChecks() { diff --git a/libqpdf/QPDFJob.cc b/libqpdf/QPDFJob.cc index d78a351c..a65db073 100644 --- a/libqpdf/QPDFJob.cc +++ b/libqpdf/QPDFJob.cc @@ -326,6 +326,7 @@ QPDFJob::QPDFJob() : verbose(false), progress(false), suppress_warnings(false), + warnings_exit_zero(false), copy_encryption(false), encryption_file(0), encryption_file_password(0), @@ -529,6 +530,12 @@ QPDFJob::suppressWarnings() } bool +QPDFJob::warningsExitZero() +{ + return this->warnings_exit_zero; +} + +bool QPDFJob::checkRequiresPassword() { return this->check_requires_password; diff --git a/libqpdf/QPDFJob_argv.cc b/libqpdf/QPDFJob_argv.cc new file mode 100644 index 00000000..421099d1 --- /dev/null +++ b/libqpdf/QPDFJob_argv.cc @@ -0,0 +1,2394 @@ +#include <qpdf/QPDFJob.hh> + +#include <iostream> +#include <string.h> +#include <stdlib.h> +#include <cstdio> +#include <ctype.h> +#include <memory> + +#include <qpdf/QUtil.hh> +#include <qpdf/QTC.hh> +#include <qpdf/QPDFCryptoProvider.hh> +#include <qpdf/QPDFArgParser.hh> +#include <qpdf/QPDFJob.hh> +#include <qpdf/QIntC.hh> + +namespace +{ + class ArgParser + { + public: + ArgParser(QPDFArgParser& ap, QPDFJob& o); + void parseOptions(); + + private: + static constexpr char const* O_PAGES = "pages"; + static constexpr char const* O_ENCRYPT = "encryption"; + static constexpr char const* O_ENCRYPT_40 = "40-bit encryption"; + static constexpr char const* O_ENCRYPT_128 = "128-bit encryption"; + static constexpr char const* O_ENCRYPT_256 = "256-bit encryption"; + static constexpr char const* O_UNDER_OVERLAY = "underlay/overlay"; + static constexpr char const* O_ATTACHMENT = "attachment"; + static constexpr char const* O_COPY_ATTACHMENT = "copy attachment"; + + void argHelp(); + void argVersion(); + void argCopyright(); + void argJsonHelp(); + void argShowCrypto(); + void argPositional(char* arg); + void argPassword(char* parameter); + void argPasswordFile(char* parameter); + void argEmpty(); + void argLinearize(); + void argEncrypt(); + void argDecrypt(); + void argPasswordIsHexKey(); + void argAllowInsecure(); + void argAllowWeakCrypto(); + void argPasswordMode(char* parameter); + void argSuppressPasswordRecovery(); + void argCopyEncryption(char* parameter); + void argEncryptionFilePassword(char* parameter); + void argPages(); + void argPagesPassword(char* parameter); + void argPagesPositional(char* parameter); + void argEndPages(); + void argUnderlay(); + void argOverlay(); + void argRotate(char* parameter); + void argCollate(char* parameter); + void argFlattenRotation(); + void argListAttachments(); + void argShowAttachment(char* parameter); + void argRemoveAttachment(char* parameter); + void argAddAttachment(); + void argCopyAttachments(); + void argStreamData(char* parameter); + void argCompressStreams(char* parameter); + void argRecompressFlate(); + void argCompressionLevel(char* parameter); + void argDecodeLevel(char* parameter); + void argNormalizeContent(char* parameter); + void argSuppressRecovery(); + void argObjectStreams(char* parameter); + void argIgnoreXrefStreams(); + void argQdf(); + void argPreserveUnreferenced(); + void argPreserveUnreferencedResources(); + void argRemoveUnreferencedResources(char* parameter); + void argKeepFilesOpen(char* parameter); + void argKeepFilesOpenThreshold(char* parameter); + void argNewlineBeforeEndstream(); + void argLinearizePass1(char* parameter); + void argCoalesceContents(); + void argFlattenAnnotations(char* parameter); + void argGenerateAppearances(); + void argMinVersion(char* parameter); + void argForceVersion(char* parameter); + void argSplitPages(char* parameter); + void argVerbose(); + void argProgress(); + void argNoWarn(); + void argWarningExitZero(); + void argDeterministicId(); + void argStaticId(); + void argStaticAesIv(); + void argNoOriginalObjectIds(); + void argShowEncryption(); + void argShowEncryptionKey(); + void argCheckLinearization(); + void argShowLinearization(); + void argShowXref(); + void argShowObject(char* parameter); + void argRawStreamData(); + void argFilteredStreamData(); + void argShowNpages(); + void argShowPages(); + void argWithImages(); + void argJson(); + void argJsonKey(char* parameter); + void argJsonObject(char* parameter); + void argCheck(); + void argOptimizeImages(); + void argExternalizeInlineImages(); + void argKeepInlineImages(); + void argRemovePageLabels(); + void argOiMinWidth(char* parameter); + void argOiMinHeight(char* parameter); + void argOiMinArea(char* parameter); + void argIiMinBytes(char* parameter); + void arg40Print(char* parameter); + void arg40Modify(char* parameter); + void arg40Extract(char* parameter); + void arg40Annotate(char* parameter); + void arg128Accessibility(char* parameter); + void arg128Extract(char* parameter); + void arg128Print(char* parameter); + void arg128Modify(char* parameter); + void arg128ClearTextMetadata(); + void arg128Assemble(char* parameter); + void arg128Annotate(char* parameter); + void arg128Form(char* parameter); + void arg128ModOther(char* parameter); + void arg128UseAes(char* parameter); + void arg128ForceV4(); + void arg256ForceR5(); + void argEncryptPositional(char* arg); + void argEndEncrypt(); + void argUOpositional(char* arg); + void argUOto(char* parameter); + void argUOfrom(char* parameter); + void argUOrepeat(char* parameter); + void argUOpassword(char* parameter); + void argEndUnderOverlay(); + void argReplaceInput(); + void argIsEncrypted(); + void argRequiresPassword(); + void argAApositional(char* arg); + void argAAKey(char* parameter); + void argAAFilename(char* parameter); + void argAACreationDate(char* parameter); + void argAAModDate(char* parameter); + void argAAMimeType(char* parameter); + void argAADescription(char* parameter); + void argAAReplace(); + void argEndAddAttachment(); + void argCApositional(char* arg); + void argCAprefix(char* parameter); + void argCApassword(char* parameter); + void argEndCopyAttachments(); + + void usage(std::string const& message); + void initOptionTable(); + void doFinalChecks(); + void parseUnderOverlayOptions(QPDFJob::UnderOverlay*); + void parseRotationParameter(std::string const&); + std::vector<int> parseNumrange(char const* range, int max, + bool throw_error = false); + + QPDFArgParser ap; + QPDFJob& o; + std::vector<char*> accumulated_args; // points to member in ap + char* pages_password; + }; +} + +ArgParser::ArgParser(QPDFArgParser& ap, QPDFJob& o) : + ap(ap), + o(o), + pages_password(nullptr) +{ + initOptionTable(); +} + +void +ArgParser::initOptionTable() +{ + auto b = [this](void (ArgParser::*f)()) { + return QPDFArgParser::bindBare(f, this); + }; + auto p = [this](void (ArgParser::*f)(char *)) { + return QPDFArgParser::bindParam(f, this); + }; + + this->ap.addFinalCheck(b(&ArgParser::doFinalChecks)); + + this->ap.selectHelpOptionTable(); + this->ap.addBare("help", b(&ArgParser::argHelp)); + this->ap.addBare("version", b(&ArgParser::argVersion)); + this->ap.addBare("copyright", b(&ArgParser::argCopyright)); + this->ap.addBare("json-help", b(&ArgParser::argJsonHelp)); + this->ap.addBare("show-crypto", b(&ArgParser::argShowCrypto)); + + this->ap.selectMainOptionTable(); + char const* yn[] = {"y", "n", 0}; + this->ap.addPositional(p(&ArgParser::argPositional)); + this->ap.addRequiredParameter("password", + p(&ArgParser::argPassword), "password"); + this->ap.addRequiredParameter("password-file", + p(&ArgParser::argPasswordFile), "password-file"); + this->ap.addBare("empty", b(&ArgParser::argEmpty)); + this->ap.addBare("linearize", b(&ArgParser::argLinearize)); + this->ap.addBare("decrypt", b(&ArgParser::argDecrypt)); + this->ap.addBare("password-is-hex-key", b(&ArgParser::argPasswordIsHexKey)); + this->ap.addBare("suppress-password-recovery", + b(&ArgParser::argSuppressPasswordRecovery)); + char const* password_mode_choices[] = + {"bytes", "hex-bytes", "unicode", "auto", 0}; + this->ap.addRequiredChoices("password-mode", + p(&ArgParser::argPasswordMode), password_mode_choices); + this->ap.addRequiredParameter("copy-encryption", + p(&ArgParser::argCopyEncryption), "file"); + this->ap.addRequiredParameter("encryption-file-password", + p(&ArgParser::argEncryptionFilePassword), "password"); + this->ap.addRequiredParameter("rotate", + p(&ArgParser::argRotate), "[+|-]angle:page-range"); + char const* stream_data_choices[] = + {"compress", "preserve", "uncompress", 0}; + this->ap.addOptionalParameter("collate",p(&ArgParser::argCollate)); + this->ap.addBare("flatten-rotation", b(&ArgParser::argFlattenRotation)); + this->ap.addBare("list-attachments", b(&ArgParser::argListAttachments)); + this->ap.addRequiredParameter("show-attachment", + p(&ArgParser::argShowAttachment), "attachment-key"); + this->ap.addRequiredParameter("remove-attachment", + p(&ArgParser::argRemoveAttachment), "attachment-key"); + this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment)); + this->ap.addBare( + "copy-attachments-from", b(&ArgParser::argCopyAttachments)); + this->ap.addRequiredChoices("stream-data", + p(&ArgParser::argStreamData), stream_data_choices); + this->ap.addRequiredChoices("compress-streams", + p(&ArgParser::argCompressStreams), yn); + this->ap.addBare("recompress-flate", b(&ArgParser::argRecompressFlate)); + this->ap.addRequiredParameter("compression-level", + p(&ArgParser::argCompressionLevel), "level"); + char const* decode_level_choices[] = + {"none", "generalized", "specialized", "all", 0}; + this->ap.addRequiredChoices("decode-level", + p(&ArgParser::argDecodeLevel), decode_level_choices); + this->ap.addRequiredChoices("normalize-content", + p(&ArgParser::argNormalizeContent), yn); + this->ap.addBare("suppress-recovery", b(&ArgParser::argSuppressRecovery)); + char const* object_streams_choices[] = { + "disable", "preserve", "generate", 0}; + this->ap.addRequiredChoices("object-streams", + p(&ArgParser::argObjectStreams), object_streams_choices); + this->ap.addBare( + "ignore-xref-streams", b(&ArgParser::argIgnoreXrefStreams)); + this->ap.addBare("qdf", b(&ArgParser::argQdf)); + this->ap.addBare( + "preserve-unreferenced", b(&ArgParser::argPreserveUnreferenced)); + this->ap.addBare( + "preserve-unreferenced-resources", + b(&ArgParser::argPreserveUnreferencedResources)); + char const* remove_unref_choices[] = { + "auto", "yes", "no", 0}; + this->ap.addRequiredChoices("remove-unreferenced-resources", + p(&ArgParser::argRemoveUnreferencedResources), remove_unref_choices); + this->ap.addRequiredChoices("keep-files-open", + p(&ArgParser::argKeepFilesOpen), yn); + this->ap.addRequiredParameter("keep-files-open-threshold", + p(&ArgParser::argKeepFilesOpenThreshold), "count"); + this->ap.addBare("newline-before-endstream", b(&ArgParser::argNewlineBeforeEndstream)); + this->ap.addRequiredParameter("linearize-pass1", + p(&ArgParser::argLinearizePass1), "filename"); + this->ap.addBare("coalesce-contents", b(&ArgParser::argCoalesceContents)); + char const* flatten_choices[] = {"all", "print", "screen", 0}; + this->ap.addRequiredChoices("flatten-annotations", + p(&ArgParser::argFlattenAnnotations), flatten_choices); + this->ap.addBare("generate-appearances", b(&ArgParser::argGenerateAppearances)); + this->ap.addRequiredParameter("min-version", + p(&ArgParser::argMinVersion), "version"); + this->ap.addRequiredParameter("force-version", + p(&ArgParser::argForceVersion), "version"); + this->ap.addOptionalParameter("split-pages",p(&ArgParser::argSplitPages)); + this->ap.addBare("verbose", b(&ArgParser::argVerbose)); + this->ap.addBare("progress", b(&ArgParser::argProgress)); + this->ap.addBare("no-warn", b(&ArgParser::argNoWarn)); + this->ap.addBare("warning-exit-0", b(&ArgParser::argWarningExitZero)); + this->ap.addBare("deterministic-id", b(&ArgParser::argDeterministicId)); + this->ap.addBare("static-id", b(&ArgParser::argStaticId)); + this->ap.addBare("static-aes-iv", b(&ArgParser::argStaticAesIv)); + this->ap.addBare("no-original-object-ids", b(&ArgParser::argNoOriginalObjectIds)); + this->ap.addBare("show-encryption", b(&ArgParser::argShowEncryption)); + this->ap.addBare("show-encryption-key", b(&ArgParser::argShowEncryptionKey)); + this->ap.addBare("check-linearization", b(&ArgParser::argCheckLinearization)); + this->ap.addBare("show-linearization", b(&ArgParser::argShowLinearization)); + this->ap.addBare("show-xref", b(&ArgParser::argShowXref)); + this->ap.addRequiredParameter("show-object", + p(&ArgParser::argShowObject), "trailer|obj[,gen]"); + this->ap.addBare("raw-stream-data", b(&ArgParser::argRawStreamData)); + this->ap.addBare("filtered-stream-data", b(&ArgParser::argFilteredStreamData)); + this->ap.addBare("show-npages", b(&ArgParser::argShowNpages)); + 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[] = { + "objects", "objectinfo", "pages", "pagelabels", "outlines", + "acroform", "encrypt", "attachments", 0}; + this->ap.addRequiredChoices("json-key", + p(&ArgParser::argJsonKey), json_key_choices); + this->ap.addRequiredParameter("json-object", + p(&ArgParser::argJsonObject), "trailer|obj[,gen]"); + this->ap.addBare("check", b(&ArgParser::argCheck)); + this->ap.addBare("optimize-images", b(&ArgParser::argOptimizeImages)); + this->ap.addBare("externalize-inline-images", b(&ArgParser::argExternalizeInlineImages)); + this->ap.addBare("keep-inline-images", b(&ArgParser::argKeepInlineImages)); + this->ap.addBare("remove-page-labels", b(&ArgParser::argRemovePageLabels)); + this->ap.addRequiredParameter("oi-min-width", + p(&ArgParser::argOiMinWidth), "minimum-width"); + this->ap.addRequiredParameter("oi-min-height", + p(&ArgParser::argOiMinHeight), "minimum-height"); + this->ap.addRequiredParameter("oi-min-area", + p(&ArgParser::argOiMinArea), "minimum-area"); + this->ap.addRequiredParameter("ii-min-bytes", + p(&ArgParser::argIiMinBytes), "minimum-bytes"); + this->ap.addBare("overlay", b(&ArgParser::argOverlay)); + this->ap.addBare("underlay", b(&ArgParser::argUnderlay)); + this->ap.addBare("replace-input", b(&ArgParser::argReplaceInput)); + this->ap.addBare("is-encrypted", b(&ArgParser::argIsEncrypted)); + this->ap.addBare("requires-password", b(&ArgParser::argRequiresPassword)); + this->ap.addBare("allow-weak-crypto", b(&ArgParser::argAllowWeakCrypto)); + + this->ap.selectMainOptionTable(); + this->ap.addBare("pages", b(&ArgParser::argPages)); + this->ap.registerOptionTable(O_PAGES, b(&ArgParser::argEndPages)); + this->ap.addRequiredParameter( + "password", p(&ArgParser::argPagesPassword), "password"); + this->ap.addPositional(p(&ArgParser::argPagesPositional)); + + this->ap.selectMainOptionTable(); + this->ap.addBare("encrypt", b(&ArgParser::argEncrypt)); + this->ap.registerOptionTable(O_ENCRYPT, b(&ArgParser::argEndEncrypt)); + this->ap.addPositional(p(&ArgParser::argEncryptPositional)); + this->ap.registerOptionTable(O_ENCRYPT_40, b(&ArgParser::argEndEncrypt)); + this->ap.addRequiredChoices("extract",p(&ArgParser::arg40Extract), yn); + this->ap.addRequiredChoices("annotate",p(&ArgParser::arg40Annotate), yn); + this->ap.addRequiredChoices("print",p(&ArgParser::arg40Print), yn); + this->ap.addRequiredChoices("modify",p(&ArgParser::arg40Modify), yn); + this->ap.registerOptionTable(O_ENCRYPT_128, b(&ArgParser::argEndEncrypt)); + this->ap.registerOptionTable(O_ENCRYPT_256, b(&ArgParser::argEndEncrypt)); + for (char const* k: {O_ENCRYPT_128, O_ENCRYPT_256}) + { + this->ap.selectOptionTable(k); + this->ap.addRequiredChoices("accessibility", + p(&ArgParser::arg128Accessibility), yn); + this->ap.addRequiredChoices("extract", p(&ArgParser::arg128Extract), yn); + char const* print128_choices[] = {"full", "low", "none", 0}; + this->ap.addRequiredChoices("print", + p(&ArgParser::arg128Print), print128_choices); + this->ap.addRequiredChoices("assemble",p(&ArgParser::arg128Assemble), yn); + this->ap.addRequiredChoices("annotate",p(&ArgParser::arg128Annotate), yn); + this->ap.addRequiredChoices("form",p(&ArgParser::arg128Form), yn); + this->ap.addRequiredChoices("modify-other",p(&ArgParser::arg128ModOther), yn); + char const* modify128_choices[] = + {"all", "annotate", "form", "assembly", "none", 0}; + this->ap.addRequiredChoices("modify", + p(&ArgParser::arg128Modify), modify128_choices); + this->ap.addBare("cleartext-metadata", b(&ArgParser::arg128ClearTextMetadata)); + } + + this->ap.selectOptionTable(O_ENCRYPT_128); + this->ap.addRequiredChoices("use-aes",p(&ArgParser::arg128UseAes), yn); + this->ap.addBare("force-V4", b(&ArgParser::arg128ForceV4)); + + this->ap.selectOptionTable(O_ENCRYPT_256); + this->ap.addBare("force-R5", b(&ArgParser::arg256ForceR5)); + this->ap.addBare("allow-insecure", b(&ArgParser::argAllowInsecure)); + + this->ap.registerOptionTable(O_UNDER_OVERLAY, b(&ArgParser::argEndUnderOverlay)); + this->ap.addPositional(p(&ArgParser::argUOpositional)); + this->ap.addRequiredParameter("to", + p(&ArgParser::argUOto), "page-range"); + this->ap.addRequiredParameter("from", + p(&ArgParser::argUOfrom), "page-range"); + this->ap.addRequiredParameter("repeat", + p(&ArgParser::argUOrepeat), "page-range"); + this->ap.addRequiredParameter("password", + p(&ArgParser::argUOpassword), "password"); + + this->ap.registerOptionTable(O_ATTACHMENT, b(&ArgParser::argEndAddAttachment)); + this->ap.addPositional(p(&ArgParser::argAApositional)); + this->ap.addRequiredParameter("key", + p(&ArgParser::argAAKey), "attachment-key"); + this->ap.addRequiredParameter("filename", + p(&ArgParser::argAAFilename), "filename"); + this->ap.addRequiredParameter("creationdate", + p(&ArgParser::argAACreationDate), "creation-date"); + this->ap.addRequiredParameter("moddate", + p(&ArgParser::argAAModDate), "modification-date"); + this->ap.addRequiredParameter("mimetype", + p(&ArgParser::argAAMimeType), "mime/type"); + this->ap.addRequiredParameter("description", + p(&ArgParser::argAADescription), "description"); + this->ap.addBare("replace", b(&ArgParser::argAAReplace)); + + this->ap.registerOptionTable(O_COPY_ATTACHMENT, b(&ArgParser::argEndCopyAttachments)); + this->ap.addPositional(p(&ArgParser::argCApositional)); + this->ap.addRequiredParameter("prefix", + p(&ArgParser::argCAprefix), "prefix"); + this->ap.addRequiredParameter("password", + p(&ArgParser::argCApassword), "password"); +} + +void +ArgParser::argPositional(char* arg) +{ + if (o.infilename == 0) + { + o.infilename = arg; + } + else if (o.outfilename == 0) + { + o.outfilename = arg; + } + else + { + usage(std::string("unknown argument ") + arg); + } +} + +void +ArgParser::argVersion() +{ + auto whoami = this->ap.getProgname(); + std::cout + << whoami << " version " << QPDF::QPDFVersion() << std::endl + << "Run " << whoami << " --copyright to see copyright and license information." + << std::endl; +} + +void +ArgParser::argCopyright() +{ + // Make sure the output looks right on an 80-column display. + // 1 2 3 4 5 6 7 8 + // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + std::cout + << this->ap.getProgname() + << " version " << QPDF::QPDFVersion() << std::endl + << std::endl + << "Copyright (c) 2005-2021 Jay Berkenbilt" + << std::endl + << "QPDF is licensed under the Apache License, Version 2.0 (the \"License\");" + << std::endl + << "you may not use this file except in compliance with the License." + << std::endl + << "You may obtain a copy of the License at" + << std::endl + << std::endl + << " http://www.apache.org/licenses/LICENSE-2.0" + << std::endl + << std::endl + << "Unless required by applicable law or agreed to in writing, software" + << std::endl + << "distributed under the License is distributed on an \"AS IS\" BASIS," + << std::endl + << "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." + << std::endl + << "See the License for the specific language governing permissions and" + << std::endl + << "limitations under the License." + << std::endl + << std::endl + << "Versions of qpdf prior to version 7 were released under the terms" + << std::endl + << "of version 2.0 of the Artistic License. At your option, you may" + << std::endl + << "continue to consider qpdf to be licensed under those terms. Please" + << std::endl + << "see the manual for additional information." + << std::endl; +} + +void +ArgParser::argHelp() +{ + std::cout + // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + << "Usage: qpdf [options] {infile | --empty} [page_selection_options] outfile\n" + << "\n" + << "An option summary appears below. Please see the documentation for details.\n" + << "\n" + << "If @filename appears anywhere in the command-line, each line of filename\n" + << "will be interpreted as an argument. No interpolation is done. Line\n" + << "terminators are stripped, but leading and trailing whitespace is\n" + << "intentionally preserved. @- can be specified to read from standard input.\n" + << "\n" + << "The output file can be - to indicate writing to standard output, or it can\n" + << "be --replace-input to cause qpdf to replace the input file with the output.\n" + << "\n" + << "Note that when contradictory options are provided, whichever options are\n" + << "provided last take precedence.\n" + << "\n" + << "\n" + << "Basic Options\n" + << "-------------\n" + << "\n" + << "--version show version of qpdf\n" + << "--copyright show qpdf's copyright and license information\n" + << "--help show command-line argument help\n" + << "--show-crypto show supported crypto providers; default is first\n" + << "--completion-bash output a bash complete command you can eval\n" + << "--completion-zsh output a zsh complete command you can eval\n" + << "--password=password specify a password for accessing encrypted files\n" + << "--password-file=file get the password the first line \"file\"; use \"-\"\n" + << " to read the password from stdin (without prompt or\n" + << " disabling echo, so use with caution)\n" + << "--is-encrypted silently exit 0 if the file is encrypted or 2\n" + << " if not; useful for shell scripts\n" + << "--requires-password silently exit 0 if a password (other than as\n" + << " supplied) is required, 2 if the file is not\n" + << " encrypted, or 3 if the file is encrypted\n" + << " but requires no password or the supplied password\n" + << " is correct; useful for shell scripts\n" + << "--verbose provide additional informational output\n" + << "--progress give progress indicators while writing output\n" + << "--no-warn suppress warnings\n" + << "--warning-exit-0 exit with code 0 instead of 3 if there are warnings\n" + << "--linearize generated a linearized (web optimized) file\n" + << "--replace-input use in place of specifying an output file; qpdf will\n" + << " replace the input file with the output\n" + << "--copy-encryption=file copy encryption parameters from specified file\n" + << "--encryption-file-password=password\n" + << " password used to open the file from which encryption\n" + << " parameters are being copied\n" + << "--allow-weak-crypto allow creation of files using weak cryptographic\n" + << " algorithms\n" + << "--encrypt options -- generate an encrypted file\n" + << "--decrypt remove any encryption on the file\n" + << "--password-is-hex-key treat primary password option as a hex-encoded key\n" + << "--suppress-password-recovery\n" + << " do not attempt recovering from password string\n" + << " encoding errors\n" + << "--password-mode=mode control qpdf's encoding of passwords\n" + << "--pages options -- select specific pages from one or more files\n" + << "--collate=n causes files specified in --pages to be collated\n" + << " in groups of n pages (default 1) rather than\n" + << " concatenated\n" + << "--flatten-rotation move page rotation from /Rotate key to content\n" + << "--rotate=[+|-]angle[:page-range]\n" + << " rotate each specified page 0, 90, 180, or 270\n" + << " degrees; rotate all pages if no page range is given\n" + << "--split-pages=[n] write each output page to a separate file\n" + << "--overlay options -- overlay pages from another file\n" + << "--underlay options -- underlay pages from another file\n" + << "\n" + << "Note that you can use the @filename or @- syntax for any argument at any\n" + << "point in the command. This provides a good way to specify a password without\n" + << "having to explicitly put it on the command line. @filename or @- must be a\n" + << "word by itself. Syntax such as --arg=@filename doesn't work.\n" + << "\n" + << "If none of --copy-encryption, --encrypt or --decrypt are given, qpdf will\n" + << "preserve any encryption data associated with a file.\n" + << "\n" + << "Note that when copying encryption parameters from another file, all\n" + << "parameters will be copied, including both user and owner passwords, even\n" + << "if the user password is used to open the other file. This works even if\n" + << "the owner password is not known.\n" + << "\n" + << "The --password-is-hex-key option overrides the normal computation of\n" + << "encryption keys. It only applies to the password used to open the main\n" + << "file. This option is not ordinarily useful but can be helpful for forensic\n" + << "or investigatory purposes. See manual for further discussion.\n" + << "\n" + << "The --rotate flag can be used to specify pages to rotate pages either\n" + << "0, 90, 180, or 270 degrees. The page range is specified in the same\n" + << "format as with the --pages option, described below. Repeat the option\n" + << "to rotate multiple groups of pages. If the angle is preceded by + or -,\n" + << "it is added to or subtracted from the original rotation. Otherwise, the\n" + << "rotation angle is set explicitly to the given value. You almost always\n" + << "want to use + or - unless you are certain about the internals of the PDF\n" + << "you are working with.\n" + << "\n" + << "If --split-pages is specified, each page is written to a separate output\n" + << "file. File names are generated as follows:\n" + << "* If the string %d appears in the output file name, it is replaced with a\n" + << " zero-padded page range starting from 1\n" + << "* Otherwise, if the output file name ends in .pdf (case insensitive), a\n" + << " zero-padded page range, preceded by a dash, is inserted before the file\n" + << " extension\n" + << "* Otherwise, the file name is appended with a zero-padded page range\n" + << " preceded by a dash.\n" + << "Page ranges are single page numbers for single-page groups or first-last\n" + << "for multipage groups.\n" + << "\n" + << "\n" + << "Encryption Options\n" + << "------------------\n" + << "\n" + << " --encrypt user-password owner-password key-length flags --\n" + << "\n" + << "Note that -- terminates parsing of encryption flags.\n" + << "\n" + << "Either or both of the user password and the owner password may be\n" + << "empty strings.\n" + << "\n" + << "key-length may be 40, 128, or 256\n" + << "\n" + << "Additional flags are dependent upon key length.\n" + << "\n" + << " If 40:\n" + << "\n" + << " --print=[yn] allow printing\n" + << " --modify=[yn] allow document modification\n" + << " --extract=[yn] allow text/graphic extraction\n" + << " --annotate=[yn] allow comments and form fill-in and signing\n" + << "\n" + << " If 128:\n" + << "\n" + << " --accessibility=[yn] allow accessibility to visually impaired\n" + << " --extract=[yn] allow other text/graphic extraction\n" + << " --print=print-opt control printing access\n" + << " --assemble=[yn] allow document assembly\n" + << " --annotate=[yn] allow commenting/filling form fields\n" + << " --form=[yn] allow filling form fields\n" + << " --modify-other=[yn] allow other modifications\n" + << " --modify=modify-opt control modify access (old way)\n" + << " --cleartext-metadata prevents encryption of metadata\n" + << " --use-aes=[yn] indicates whether to use AES encryption\n" + << " --force-V4 forces use of V=4 encryption handler\n" + << "\n" + << " If 256, options are the same as 128 with these exceptions:\n" + << " --force-V4 this option is not available with 256-bit keys\n" + << " --use-aes this option is always on with 256-bit keys\n" + << " --force-R5 forces use of deprecated R=5 encryption\n" + << " --allow-insecure allow the owner password to be empty when the\n" + << " user password is not empty\n" + << "\n" + << " print-opt may be:\n" + << "\n" + << " full allow full printing\n" + << " low allow only low-resolution printing\n" + << " none disallow printing\n" + << "\n" + << " modify-opt may be:\n" + << "\n" + << " all allow full document modification\n" + << " annotate allow comment authoring and form operations\n" + << " form allow form field fill-in and signing\n" + << " assembly allow document assembly only\n" + << " none allow no modifications\n" + << "\n" + << "The default for each permission option is to be fully permissive. Please\n" + << "refer to the manual for more details on the modify options.\n" + << "\n" + << "Specifying cleartext-metadata forces the PDF version to at least 1.5.\n" + << "Specifying use of AES forces the PDF version to at least 1.6. These\n" + << "options are both off by default.\n" + << "\n" + << "The --force-V4 flag forces the V=4 encryption handler introduced in PDF 1.5\n" + << "to be used even if not otherwise needed. This option is primarily useful\n" + << "for testing qpdf and has no other practical use.\n" + << "\n" + << "A warning will be issued if you attempt to encrypt a file with a format that\n" + << "uses a weak cryptographic algorithm such as RC4. To suppress the warning,\n" + << "specify the option --allow-weak-crypto. This option is outside of encryption\n" + << "options (e.g. --allow-week-crypto --encrypt u o 128 --)\n" + << "\n" + << "\n" + << "Password Modes\n" + << "--------------\n" + << "\n" + << "The --password-mode controls how qpdf interprets passwords supplied\n" + << "on the command-line. qpdf's default behavior is correct in almost all\n" + << "cases, but you can fine-tune with this option.\n" + << "\n" + << " bytes: use the password literally as supplied\n" + << " hex-bytes: interpret the password as a hex-encoded byte string\n" + << " unicode: interpret the password as a UTF-8 encoded string\n" + << " auto: attempt to infer the encoding and adjust as needed\n" + << "\n" + << "This is a complex topic. See the manual for a complete discussion.\n" + << "\n" + << "\n" + << "Page Selection Options\n" + << "----------------------\n" + << "\n" + << "These options allow pages to be selected from one or more PDF files.\n" + << "Whatever file is given as the primary input file is used as the\n" + << "starting point, but its pages are replaced with pages as specified.\n" + << "\n" + << "--keep-files-open=[yn]\n" + << "--keep-files-open-threshold=count\n" + << "--pages file [ --password=password ] [ page-range ] ... --\n" + << "\n" + << "For each file that pages should be taken from, specify the file, a\n" + << "password needed to open the file (if any), and a page range. The\n" + << "password needs to be given only once per file. If any of the input\n" + << "files are the same as the primary input file or the file used to copy\n" + << "encryption parameters (if specified), you do not need to repeat the\n" + << "password here. The same file can be repeated multiple times. The\n" + << "filename \".\" may be used to refer to the current input file. All\n" + << "non-page data (info, outlines, page numbers, etc. are taken from the\n" + << "primary input file. To discard this, use --empty as the primary\n" + << "input.\n" + << "\n" + << "By default, when more than 200 distinct files are specified, qpdf will\n" + << "close each file when not being referenced. With 200 files or fewer, all\n" + << "files will be kept open at the same time. This behavior can be overridden\n" + << "by specifying --keep-files-open=[yn]. Closing and opening files can have\n" + << "very high overhead on certain file systems, especially networked file\n" + << "systems. The threshold of 200 can be modified with\n" + << "--keep-files-open-threshold\n" + << "\n" + << "The page range is a set of numbers separated by commas, ranges of\n" + << "numbers separated dashes, or combinations of those. The character\n" + << "\"z\" represents the last page. A number preceded by an \"r\" indicates\n" + << "to count from the end, so \"r3-r1\" would be the last three pages of the\n" + << "document. Pages can appear in any order. Ranges can appear with a\n" + << "high number followed by a low number, which causes the pages to appear in\n" + << "reverse. Numbers may be repeated. A page range may be appended with :odd\n" + << "to indicate odd pages in the selected range or :even to indicate even\n" + << "pages.\n" + << "\n" + << "If the page range is omitted, the range of 1-z is assumed. qpdf decides\n" + << "that the page range is omitted if the range argument is either -- or a\n" + << "valid file name and not a valid range.\n" + << "\n" + << "The usual behavior of --pages is to add all pages from the first file,\n" + << "then all pages from the second file, and so on. If the --collate option\n" + << "is specified, then pages are collated instead. In other words, qpdf takes\n" + << "the first page from the first file, the first page from the second file,\n" + << "and so on until it runs out of files; then it takes the second page from\n" + << "each file, etc. When a file runs out of pages, it is skipped until all\n" + << "specified pages are taken from all files.\n" + << "\n" + << "See the manual for examples and a discussion of additional subtleties.\n" + << "\n" + << "\n" + << "Overlay and Underlay Options\n" + << "----------------------------\n" + << "\n" + << "These options allow pages from another file to be overlaid or underlaid\n" + << "on the primary output. Overlaid pages are drawn on top of the destination\n" + << "page and may obscure the page. Underlaid pages are drawn below the\n" + << "destination page.\n" + << "\n" + << "{--overlay | --underlay } file\n" + " [ --password=password ]\n" + " [ --to=page-range ]\n" + " [ --from=[page-range] ]\n" + " [ --repeat=page-range ]\n" + " --\n" + << "\n" + << "For overlay and underlay, a file and optional password are specified, along\n" + << "with a series of optional page ranges. The default behavior is that each\n" + << "page of the overlay or underlay file is imposed on the corresponding page\n" + << "of the primary output until it runs out of pages, and any extra pages are\n" + << "ignored. The page range options all take page ranges in the same form as\n" + << "the --pages option. They have the following meanings:\n" + << "\n" + << " --to: the pages in the primary output to which overlay/underlay is\n" + << " applied\n" + << " --from: the pages from the overlay/underlay file that are used\n" + << " --repeat: pages from the overlay/underlay that are repeated after\n" + << " any \"from\" pages have been exhausted\n" + << "\n" + << "\n" + << "Embedded Files/Attachments Options\n" + << "----------------------------------\n" + << "\n" + << "These options can be used to work with embedded files, also known as\n" + << "attachments.\n" + << "\n" + << "--list-attachments show key and stream number for embedded files;\n" + << " combine with --verbose for more detailed information\n" + << "--show-attachment=key write the contents of the specified attachment to\n" + << " standard output as binary data\n" + << "--add-attachment file options --\n" + << " add or replace an attachment\n" + << "--remove-attachment=key remove the specified attachment; repeatable\n" + << "--copy-attachments-from file options --\n" + << " copy attachments from another file\n" + << "\n" + << "The \"key\" option is the unique name under which the attachment is registered\n" + << "within the PDF file. You can get this using the --list-attachments option. This\n" + << "is usually the same as the filename, but it doesn't have to be.\n" + << "\n" + << "Options for adding attachments:\n" + << "\n" + << " file path to the file to attach\n" + << " --key=key the name of this in the embedded files table;\n" + << " defaults to the last path element of file\n" + << " --filename=name the file name of the attachment; this is what is\n" + << " usually displayed to the user; defaults to the\n" + << " last path element of file\n" + << " --creationdate=date creation date in PDF format; defaults to the\n" + << " current time\n" + << " --moddate=date modification date in PDF format; defaults to the\n" + << " current time\n" + << " --mimetype=type/subtype mime type of attachment (e.g. application/pdf)\n" + << " --description=\"text\" attachment description\n" + << " --replace replace any existing attachment with the same key\n" + << "\n" + << "Options for copying attachments:\n" + << "\n" + << " file file whose attachments should be copied\n" + << " --password=password password to open the other file, if needed\n" + << " --prefix=prefix a prefix to insert in front of each key;\n" + << " required if needed to ensure each attachment\n" + << " has a unique key\n" + << "\n" + << "Date format: D:yyyymmddhhmmss<z> where <z> is either Z for UTC or a timezone\n" + << "offset in the form -hh'mm' or +hh'mm'.\n" + << "Examples: D:20210207161528-05'00', D:20210207211528Z\n" + << "\n" + << "\n" + << "Advanced Parsing Options\n" + << "------------------------\n" + << "\n" + << "These options control aspects of how qpdf reads PDF files. Mostly these are\n" + << "of use to people who are working with damaged files. There is little reason\n" + << "to use these options unless you are trying to solve specific problems.\n" + << "\n" + << "--suppress-recovery prevents qpdf from attempting to recover damaged files\n" + << "--ignore-xref-streams tells qpdf to ignore any cross-reference streams\n" + << "\n" + << "\n" + << "Advanced Transformation Options\n" + << "-------------------------------\n" + << "\n" + << "These transformation options control fine points of how qpdf creates\n" + << "the output file. Mostly these are of use only to people who are very\n" + << "familiar with the PDF file format or who are PDF developers.\n" + << "\n" + << "--stream-data=option controls transformation of stream data (below)\n" + << "--compress-streams=[yn] controls whether to compress streams on output\n" + << "--decode-level=option controls how to filter streams from the input\n" + << "--recompress-flate recompress streams already compressed with Flate\n" + << "--compression-level=n set zlib compression level; most effective with\n" + << " --recompress-flate --object-streams=generate\n" + << "--normalize-content=[yn] enables or disables normalization of content streams\n" + << "--object-streams=mode controls handing of object streams\n" + << "--preserve-unreferenced preserve unreferenced objects\n" + << "--remove-unreferenced-resources={auto,yes,no}\n" + << " whether to remove unreferenced page resources\n" + << "--preserve-unreferenced-resources\n" + << " synonym for --remove-unreferenced-resources=no\n" + << "--newline-before-endstream always put a newline before endstream\n" + << "--coalesce-contents force all pages' content to be a single stream\n" + << "--flatten-annotations=option\n" + << " incorporate rendering of annotations into page\n" + << " contents including those for interactive form\n" + << " fields; may also want --generate-appearances\n" + << "--generate-appearances generate appearance streams for form fields\n" + << "--optimize-images compress images with DCT (JPEG) when advantageous\n" + << "--oi-min-width=w do not optimize images whose width is below w;\n" + << " default is 128. Use 0 to mean no minimum\n" + << "--oi-min-height=h do not optimize images whose height is below h\n" + << " default is 128. Use 0 to mean no minimum\n" + << "--oi-min-area=a do not optimize images whose pixel count is below a\n" + << " default is 16,384. Use 0 to mean no minimum\n" + << "--externalize-inline-images convert inline images to regular images; by\n" + << " default, images of at least 1,024 bytes are\n" + << " externalized\n" + << "--ii-min-bytes=bytes specify minimum size of inline images to be\n" + << " converted to regular images\n" + << "--keep-inline-images exclude inline images from image optimization\n" + << "--remove-page-labels remove any page labels present in the output file\n" + << "--qdf turns on \"QDF mode\" (below)\n" + << "--linearize-pass1=file write intermediate pass of linearized file\n" + << " for debugging\n" + << "--min-version=version sets the minimum PDF version of the output file\n" + << "--force-version=version forces this to be the PDF version of the output file\n" + << "\n" + << "Options for --flatten-annotations are all, print, or screen. If the option\n" + << "is print, only annotations marked as print are included. If the option is\n" + << "screen, options marked as \"no view\" are excluded. Otherwise, annotations\n" + << "are flattened regardless of the presence of print or NoView flags. It is\n" + << "common for PDF files to have a flag set that appearance streams need to be\n" + << "regenerated. This happens when someone changes a form value with software\n" + << "that does not know how to render the new value. qpdf will not flatten form\n" + << "fields in files like this. If you get this warning, you have two choices:\n" + << "either use qpdf's --generate-appearances flag to tell qpdf to go ahead and\n" + << "regenerate appearances, or use some other tool to generate the appearances.\n" + << "qpdf does a pretty good job with most forms when only ASCII and \"Windows\n" + << "ANSI\" characters are used in form field values, but if your form fields\n" + << "contain other characters, rich text, or are other than left justified, you\n" + << "will get better results first saving with other software.\n" + << "\n" + << "Version numbers may be expressed as major.minor.extension-level, so 1.7.3\n" + << "means PDF version 1.7 at extension level 3.\n" + << "\n" + << "Values for stream data options:\n" + << "\n" + << " compress recompress stream data when possible (default)\n" + << " preserve leave all stream data as is\n" + << " uncompress uncompress stream data when possible\n" + << "\n" + << "Values for object stream mode:\n" + << "\n" + << " preserve preserve original object streams (default)\n" + << " disable don't write any object streams\n" + << " generate use object streams wherever possible\n" + << "\n" + << "When --compress-streams=n is specified, this overrides the default behavior\n" + << "of qpdf, which is to attempt compress uncompressed streams. Setting\n" + << "stream data mode to uncompress or preserve has the same effect.\n" + << "\n" + << "The --decode-level parameter may be set to one of the following values:\n" + << " none do not decode streams\n" + << " generalized decode streams compressed with generalized filters\n" + << " including LZW, Flate, and the ASCII encoding filters.\n" + << " specialized additionally decode streams with non-lossy specialized\n" + << " filters including RunLength\n" + << " all additionally decode streams with lossy filters\n" + << " including DCT (JPEG)\n" + << "\n" + << "In qdf mode, by default, content normalization is turned on, and the\n" + << "stream data mode is set to uncompress. QDF mode does not support\n" + << "linearized files. The --linearize flag disables qdf mode.\n" + << "\n" + << "Setting the minimum PDF version of the output file may raise the version\n" + << "but will never lower it. Forcing the PDF version of the output file may\n" + << "set the PDF version to a lower value than actually allowed by the file's\n" + << "contents. You should only do this if you have no other possible way to\n" + << "open the file or if you know that the file definitely doesn't include\n" + << "features not supported later versions.\n" + << "\n" + << "Testing, Inspection, and Debugging Options\n" + << "------------------------------------------\n" + << "\n" + << "These options can be useful for digging into PDF files or for use in\n" + << "automated test suites for software that uses the qpdf library.\n" + << "\n" + << "--deterministic-id generate deterministic /ID\n" + << "--static-id generate static /ID: FOR TESTING ONLY!\n" + << "--static-aes-iv use a static initialization vector for AES-CBC\n" + << " This is option is not secure! FOR TESTING ONLY!\n" + << "--no-original-object-ids suppress original object ID comments in qdf mode\n" + << "--show-encryption quickly show encryption parameters\n" + << "--show-encryption-key when showing encryption, reveal the actual key\n" + << "--check-linearization check file integrity and linearization status\n" + << "--show-linearization check and show all linearization data\n" + << "--show-xref show the contents of the cross-reference table\n" + << "--show-object=trailer|obj[,gen]\n" + << " show the contents of the given object\n" + << " --raw-stream-data show raw stream data instead of object contents\n" + << " --filtered-stream-data show filtered stream data instead of object contents\n" + << "--show-npages print the number of pages in the file\n" + << "--show-pages shows the object/generation number for each page\n" + << " --with-images also shows the object IDs for images on each page\n" + << "--check check file structure + encryption, linearization\n" + << "--json generate a json representation of the file\n" + << "--json-help describe the format of the json representation\n" + << "--json-key=key repeatable; prune json structure to include only\n" + << " specified keys. If absent, all keys are shown\n" + << "--json-object=trailer|[obj,gen]\n" + << " repeatable; include only specified objects in the\n" + << " \"objects\" section of the json. If absent, all\n" + << " objects are shown\n" + << "\n" + << "The json representation generated by qpdf is designed to facilitate\n" + << "processing of qpdf from other programming languages that have a hard\n" + << "time calling C++ APIs. Run qpdf --json-help for details on the format.\n" + << "The manual has more in-depth information about the json representation\n" + << "and certain compatibility guarantees that qpdf provides.\n" + << "\n" + << "The --raw-stream-data and --filtered-stream-data options are ignored\n" + << "unless --show-object is given. Either of these options will cause the\n" + << "stream data to be written to standard output.\n" + << "\n" + << "If --filtered-stream-data is given and --normalize-content=y is also\n" + << "given, qpdf will attempt to normalize the stream data as if it is a\n" + << "page content stream. This attempt will be made even if it is not a\n" + << "page content stream, in which case it will produce unusable results.\n" + << "\n" + << "Ordinarily, qpdf exits with a status of 0 on success or a status of 2\n" + << "if any errors occurred. If there were warnings but not errors, qpdf\n" + << "exits with a status of 3. If warnings would have been issued but --no-warn\n" + << "was given, an exit status of 3 is still used. If you want qpdf to exit\n" + << "with status 0 when there are warnings, use the --warning-exit-0 flag.\n" + << "When --no-warn and --warning-exit-0 are used together, the effect is for\n" + << "qpdf to completely ignore warnings. qpdf does not use exit status 1,\n" + << "since that is used by the shell if it can't execute qpdf.\n"; +} + +void +ArgParser::argJsonHelp() +{ + // Make sure the output looks right on an 80-column display. + // 1 2 3 4 5 6 7 8 + // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 + std::cout + << "The json block below contains the same structure with the same keys as the" + << std::endl + << "json generated by qpdf. In the block below, the values are descriptions of" + << std::endl + << "the meanings of those entries. The specific contract guaranteed by qpdf in" + << std::endl + << "its json representation is explained in more detail in the manual. You can" + << std::endl + << "specify a subset of top-level keys when you invoke qpdf, but the \"version\"" + << std::endl + << "and \"parameters\" keys will always be present. Note that the \"encrypt\"" + << std::endl + << "key's values will be populated for non-encrypted files. Some values will" + << std::endl + << "be null, and others will have values that apply to unencrypted files." + << std::endl + << QPDFJob::json_schema().unparse() + << std::endl; +} + +void +ArgParser::argShowCrypto() +{ + auto crypto = QPDFCryptoProvider::getRegisteredImpls(); + std::string default_crypto = QPDFCryptoProvider::getDefaultProvider(); + std::cout << default_crypto << std::endl; + for (auto const& iter: crypto) + { + if (iter != default_crypto) + { + std::cout << iter << std::endl; + } + } +} + +void +ArgParser::argPassword(char* parameter) +{ + o.password = parameter; +} + +void +ArgParser::argPasswordFile(char* parameter) +{ + std::list<std::string> lines; + if (strcmp(parameter, "-") == 0) + { + QTC::TC("qpdf", "qpdf password stdin"); + lines = QUtil::read_lines_from_file(std::cin); + } + else + { + QTC::TC("qpdf", "qpdf password file"); + lines = QUtil::read_lines_from_file(parameter); + } + if (lines.size() >= 1) + { + // Make sure the memory for this stays in scope. + o.password_alloc = std::shared_ptr<char>( + QUtil::copy_string(lines.front().c_str()), + std::default_delete<char[]>()); + o.password = o.password_alloc.get(); + + if (lines.size() > 1) + { + std::cerr << this->ap.getProgname() + << ": WARNING: all but the first line of" + << " the password file are ignored" << std::endl; + } + } +} + +void +ArgParser::argEmpty() +{ + o.infilename = ""; +} + +void +ArgParser::argLinearize() +{ + o.linearize = true; +} + +void +ArgParser::argEncrypt() +{ + this->accumulated_args.clear(); + if (this->ap.isCompleting() && this->ap.argsLeft() == 0) + { + this->ap.insertCompletion("user-password"); + } + this->ap.selectOptionTable(O_ENCRYPT); +} + +void +ArgParser::argEncryptPositional(char* arg) +{ + this->accumulated_args.push_back(arg); + size_t n_args = this->accumulated_args.size(); + if (n_args < 3) + { + if (this->ap.isCompleting() && (this->ap.argsLeft() == 0)) + { + if (n_args == 1) + { + this->ap.insertCompletion("owner-password"); + } + else if (n_args == 2) + { + this->ap.insertCompletion("40"); + this->ap.insertCompletion("128"); + this->ap.insertCompletion("256"); + } + } + return; + } + o.user_password = this->accumulated_args.at(0); + o.owner_password = this->accumulated_args.at(1); + std::string len_str = this->accumulated_args.at(2); + if (len_str == "40") + { + o.keylen = 40; + this->ap.selectOptionTable(O_ENCRYPT_40); + } + else if (len_str == "128") + { + o.keylen = 128; + this->ap.selectOptionTable(O_ENCRYPT_128); + } + else if (len_str == "256") + { + o.keylen = 256; + o.use_aes = true; + this->ap.selectOptionTable(O_ENCRYPT_256); + } + else + { + usage("encryption key length must be 40, 128, or 256"); + } +} + +void +ArgParser::argDecrypt() +{ + o.decrypt = true; + o.encrypt = false; + o.copy_encryption = false; +} + +void +ArgParser::argPasswordIsHexKey() +{ + o.password_is_hex_key = true; +} + +void +ArgParser::argSuppressPasswordRecovery() +{ + o.suppress_password_recovery = true; +} + +void +ArgParser::argPasswordMode(char* parameter) +{ + if (strcmp(parameter, "bytes") == 0) + { + o.password_mode = QPDFJob::pm_bytes; + } + else if (strcmp(parameter, "hex-bytes") == 0) + { + o.password_mode = QPDFJob::pm_hex_bytes; + } + else if (strcmp(parameter, "unicode") == 0) + { + o.password_mode = QPDFJob::pm_unicode; + } + else if (strcmp(parameter, "auto") == 0) + { + o.password_mode = QPDFJob::pm_auto; + } + else + { + usage("invalid password-mode option"); + } +} + +void +ArgParser::argAllowInsecure() +{ + o.allow_insecure = true; +} + +void +ArgParser::argAllowWeakCrypto() +{ + o.allow_weak_crypto = true; +} + +void +ArgParser::argCopyEncryption(char* parameter) +{ + o.encryption_file = parameter; + o.copy_encryption = true; + o.encrypt = false; + o.decrypt = false; +} + +void +ArgParser::argEncryptionFilePassword(char* parameter) +{ + o.encryption_file_password = parameter; +} + +void +ArgParser::argCollate(char* parameter) +{ + auto n = ((parameter == 0) ? 1 : + QUtil::string_to_uint(parameter)); + o.collate = QIntC::to_size(n); +} + +void +ArgParser::argPages() +{ + if (! o.page_specs.empty()) + { + usage("the --pages may only be specified one time"); + } + this->accumulated_args.clear(); + this->ap.selectOptionTable(O_PAGES); +} + +void +ArgParser::argPagesPassword(char* parameter) +{ + if (this->pages_password != nullptr) + { + QTC::TC("qpdf", "qpdf duplicated pages password"); + usage("--password already specified for this file"); + } + if (this->accumulated_args.size() != 1) + { + QTC::TC("qpdf", "qpdf misplaced pages password"); + usage("in --pages, --password must immediately follow a file name"); + } + this->pages_password = parameter; +} + +void +ArgParser::argPagesPositional(char* arg) +{ + if (arg == nullptr) + { + if (this->accumulated_args.empty()) + { + return; + } + } + else + { + this->accumulated_args.push_back(arg); + } + + char const* file = this->accumulated_args.at(0); + char const* range = nullptr; + + size_t n_args = this->accumulated_args.size(); + if (n_args >= 2) + { + range = this->accumulated_args.at(1); + } + + // See if the user omitted the range entirely, in which case we + // assume "1-z". + char* next_file = nullptr; + if (range == nullptr) + { + if (arg == nullptr) + { + // The filename or password was the last argument + QTC::TC("qpdf", "qpdf pages range omitted at end", + this->pages_password == nullptr ? 0 : 1); + } + else + { + // We need to accumulate some more arguments + return; + } + } + else + { + try + { + parseNumrange(range, 0, true); + } + catch (std::runtime_error& e1) + { + // The range is invalid. Let's see if it's a file. + if (strcmp(range, ".") == 0) + { + // "." means the input file. + QTC::TC("qpdf", "qpdf pages range omitted with ."); + } + else if (QUtil::file_can_be_opened(range)) + { + QTC::TC("qpdf", "qpdf pages range omitted in middle"); + // Yup, it's a file. + } + else + { + // Give the range error + usage(e1.what()); + } + next_file = const_cast<char*>(range); + range = nullptr; + } + } + if (range == nullptr) + { + range = "1-z"; + } + o.page_specs.push_back(QPDFJob::PageSpec(file, this->pages_password, range)); + this->accumulated_args.clear(); + this->pages_password = nullptr; + if (next_file != nullptr) + { + this->accumulated_args.push_back(next_file); + } +} + +void +ArgParser::argEndPages() +{ + argPagesPositional(nullptr); + if (o.page_specs.empty()) + { + usage("--pages: no page specifications given"); + } +} + +void +ArgParser::argUnderlay() +{ + parseUnderOverlayOptions(&o.underlay); +} + +void +ArgParser::argOverlay() +{ + parseUnderOverlayOptions(&o.overlay); +} + +void +ArgParser::argRotate(char* parameter) +{ + parseRotationParameter(parameter); +} + +void +ArgParser::argFlattenRotation() +{ + o.flatten_rotation = true; +} + +void +ArgParser::argListAttachments() +{ + o.list_attachments = true; + o.require_outfile = false; +} + +void +ArgParser::argShowAttachment(char* parameter) +{ + o.attachment_to_show = parameter; + o.require_outfile = false; +} + +void +ArgParser::argRemoveAttachment(char* parameter) +{ + o.attachments_to_remove.push_back(parameter); +} + +void +ArgParser::argAddAttachment() +{ + o.attachments_to_add.push_back(QPDFJob::AddAttachment()); + this->ap.selectOptionTable(O_ATTACHMENT); +} + +void +ArgParser::argCopyAttachments() +{ + o.attachments_to_copy.push_back(QPDFJob::CopyAttachmentFrom()); + this->ap.selectOptionTable(O_COPY_ATTACHMENT); +} + +void +ArgParser::argStreamData(char* parameter) +{ + o.stream_data_set = true; + if (strcmp(parameter, "compress") == 0) + { + o.stream_data_mode = qpdf_s_compress; + } + else if (strcmp(parameter, "preserve") == 0) + { + o.stream_data_mode = qpdf_s_preserve; + } + else if (strcmp(parameter, "uncompress") == 0) + { + o.stream_data_mode = qpdf_s_uncompress; + } + else + { + // If this happens, it means streamDataChoices in + // ArgParser::initOptionTable is wrong. + usage("invalid stream-data option"); + } +} + +void +ArgParser::argCompressStreams(char* parameter) +{ + o.compress_streams_set = true; + o.compress_streams = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::argRecompressFlate() +{ + o.recompress_flate_set = true; + o.recompress_flate = true; +} + +void +ArgParser::argCompressionLevel(char* parameter) +{ + o.compression_level = QUtil::string_to_int(parameter); +} + +void +ArgParser::argDecodeLevel(char* parameter) +{ + o.decode_level_set = true; + if (strcmp(parameter, "none") == 0) + { + o.decode_level = qpdf_dl_none; + } + else if (strcmp(parameter, "generalized") == 0) + { + o.decode_level = qpdf_dl_generalized; + } + else if (strcmp(parameter, "specialized") == 0) + { + o.decode_level = qpdf_dl_specialized; + } + else if (strcmp(parameter, "all") == 0) + { + o.decode_level = qpdf_dl_all; + } + else + { + // If this happens, it means decodeLevelChoices in + // ArgParser::initOptionTable is wrong. + usage("invalid option"); + } +} + +void +ArgParser::argNormalizeContent(char* parameter) +{ + o.normalize_set = true; + o.normalize = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::argSuppressRecovery() +{ + o.suppress_recovery = true; +} + +void +ArgParser::argObjectStreams(char* parameter) +{ + o.object_stream_set = true; + if (strcmp(parameter, "disable") == 0) + { + o.object_stream_mode = qpdf_o_disable; + } + else if (strcmp(parameter, "preserve") == 0) + { + o.object_stream_mode = qpdf_o_preserve; + } + else if (strcmp(parameter, "generate") == 0) + { + o.object_stream_mode = qpdf_o_generate; + } + else + { + // If this happens, it means objectStreamsChoices in + // ArgParser::initOptionTable is wrong. + usage("invalid object stream mode"); + } +} + +void +ArgParser::argIgnoreXrefStreams() +{ + o.ignore_xref_streams = true; +} + +void +ArgParser::argQdf() +{ + o.qdf_mode = true; +} + +void +ArgParser::argPreserveUnreferenced() +{ + o.preserve_unreferenced_objects = true; +} + +void +ArgParser::argPreserveUnreferencedResources() +{ + o.remove_unreferenced_page_resources = QPDFJob::re_no; +} + +void +ArgParser::argRemoveUnreferencedResources(char* parameter) +{ + if (strcmp(parameter, "auto") == 0) + { + o.remove_unreferenced_page_resources = QPDFJob::re_auto; + } + else if (strcmp(parameter, "yes") == 0) + { + o.remove_unreferenced_page_resources = QPDFJob::re_yes; + } + else if (strcmp(parameter, "no") == 0) + { + o.remove_unreferenced_page_resources = QPDFJob::re_no; + } + else + { + // If this happens, it means remove_unref_choices in + // ArgParser::initOptionTable is wrong. + usage("invalid value for --remove-unreferenced-page-resources"); + } +} + +void +ArgParser::argKeepFilesOpen(char* parameter) +{ + o.keep_files_open_set = true; + o.keep_files_open = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::argKeepFilesOpenThreshold(char* parameter) +{ + o.keep_files_open_threshold = QUtil::string_to_uint(parameter); +} + +void +ArgParser::argNewlineBeforeEndstream() +{ + o.newline_before_endstream = true; +} + +void +ArgParser::argLinearizePass1(char* parameter) +{ + o.linearize_pass1 = parameter; +} + +void +ArgParser::argCoalesceContents() +{ + o.coalesce_contents = true; +} + +void +ArgParser::argFlattenAnnotations(char* parameter) +{ + o.flatten_annotations = true; + if (strcmp(parameter, "screen") == 0) + { + o.flatten_annotations_forbidden |= an_no_view; + } + else if (strcmp(parameter, "print") == 0) + { + o.flatten_annotations_required |= an_print; + } +} + +void +ArgParser::argGenerateAppearances() +{ + o.generate_appearances = true; +} + +void +ArgParser::argMinVersion(char* parameter) +{ + o.min_version = parameter; +} + +void +ArgParser::argForceVersion(char* parameter) +{ + o.force_version = parameter; +} + +void +ArgParser::argSplitPages(char* parameter) +{ + int n = ((parameter == 0) ? 1 : + QUtil::string_to_int(parameter)); + o.split_pages = n; +} + +void +ArgParser::argVerbose() +{ + o.verbose = true; +} + +void +ArgParser::argProgress() +{ + o.progress = true; +} + +void +ArgParser::argNoWarn() +{ + o.suppress_warnings = true; +} + +void +ArgParser::argWarningExitZero() +{ + o.warnings_exit_zero = true; +} + +void +ArgParser::argDeterministicId() +{ + o.deterministic_id = true; +} + +void +ArgParser::argStaticId() +{ + o.static_id = true; +} + +void +ArgParser::argStaticAesIv() +{ + o.static_aes_iv = true; +} + +void +ArgParser::argNoOriginalObjectIds() +{ + o.suppress_original_object_id = true; +} + +void +ArgParser::argShowEncryption() +{ + o.show_encryption = true; + o.require_outfile = false; +} + +void +ArgParser::argShowEncryptionKey() +{ + o.show_encryption_key = true; +} + +void +ArgParser::argCheckLinearization() +{ + o.check_linearization = true; + o.require_outfile = false; +} + +void +ArgParser::argShowLinearization() +{ + o.show_linearization = true; + o.require_outfile = false; +} + +void +ArgParser::argShowXref() +{ + o.show_xref = true; + o.require_outfile = false; +} + +void +ArgParser::argShowObject(char* parameter) +{ + QPDFJob::parse_object_id(parameter, o.show_trailer, o.show_obj, o.show_gen); + o.require_outfile = false; +} + +void +ArgParser::argRawStreamData() +{ + o.show_raw_stream_data = true; +} + +void +ArgParser::argFilteredStreamData() +{ + o.show_filtered_stream_data = true; +} + +void +ArgParser::argShowNpages() +{ + o.show_npages = true; + o.require_outfile = false; +} + +void +ArgParser::argShowPages() +{ + o.show_pages = true; + o.require_outfile = false; +} + +void +ArgParser::argWithImages() +{ + o.show_page_images = true; +} + +void +ArgParser::argJson() +{ + o.json = true; + o.require_outfile = false; +} + +void +ArgParser::argJsonKey(char* parameter) +{ + o.json_keys.insert(parameter); +} + +void +ArgParser::argJsonObject(char* parameter) +{ + o.json_objects.insert(parameter); +} + +void +ArgParser::argCheck() +{ + o.check = true; + o.require_outfile = false; +} + +void +ArgParser::argOptimizeImages() +{ + o.optimize_images = true; +} + +void +ArgParser::argExternalizeInlineImages() +{ + o.externalize_inline_images = true; +} + +void +ArgParser::argKeepInlineImages() +{ + o.keep_inline_images = true; +} + +void +ArgParser::argRemovePageLabels() +{ + o.remove_page_labels = true; +} + +void +ArgParser::argOiMinWidth(char* parameter) +{ + o.oi_min_width = QUtil::string_to_uint(parameter); +} + +void +ArgParser::argOiMinHeight(char* parameter) +{ + o.oi_min_height = QUtil::string_to_uint(parameter); +} + +void +ArgParser::argOiMinArea(char* parameter) +{ + o.oi_min_area = QUtil::string_to_uint(parameter); +} + +void +ArgParser::argIiMinBytes(char* parameter) +{ + o.ii_min_bytes = QUtil::string_to_uint(parameter); +} + +void +ArgParser::arg40Print(char* parameter) +{ + o.r2_print = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg40Modify(char* parameter) +{ + o.r2_modify = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg40Extract(char* parameter) +{ + o.r2_extract = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg40Annotate(char* parameter) +{ + o.r2_annotate = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg128Accessibility(char* parameter) +{ + o.r3_accessibility = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg128Extract(char* parameter) +{ + o.r3_extract = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg128Print(char* parameter) +{ + if (strcmp(parameter, "full") == 0) + { + o.r3_print = qpdf_r3p_full; + } + else if (strcmp(parameter, "low") == 0) + { + o.r3_print = qpdf_r3p_low; + } + else if (strcmp(parameter, "none") == 0) + { + o.r3_print = qpdf_r3p_none; + } + else + { + usage("invalid print option"); + } +} + +void +ArgParser::arg128Modify(char* parameter) +{ + if (strcmp(parameter, "all") == 0) + { + o.r3_assemble = true; + o.r3_annotate_and_form = true; + o.r3_form_filling = true; + o.r3_modify_other = true; + } + else if (strcmp(parameter, "annotate") == 0) + { + o.r3_assemble = true; + o.r3_annotate_and_form = true; + o.r3_form_filling = true; + o.r3_modify_other = false; + } + else if (strcmp(parameter, "form") == 0) + { + o.r3_assemble = true; + o.r3_annotate_and_form = false; + o.r3_form_filling = true; + o.r3_modify_other = false; + } + else if (strcmp(parameter, "assembly") == 0) + { + o.r3_assemble = true; + o.r3_annotate_and_form = false; + o.r3_form_filling = false; + o.r3_modify_other = false; + } + else if (strcmp(parameter, "none") == 0) + { + o.r3_assemble = false; + o.r3_annotate_and_form = false; + o.r3_form_filling = false; + o.r3_modify_other = false; + } + else + { + usage("invalid modify option"); + } +} + +void +ArgParser::arg128ClearTextMetadata() +{ + o.cleartext_metadata = true; +} + +void +ArgParser::arg128Assemble(char* parameter) +{ + o.r3_assemble = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg128Annotate(char* parameter) +{ + o.r3_annotate_and_form = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg128Form(char* parameter) +{ + o.r3_form_filling = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg128ModOther(char* parameter) +{ + o.r3_modify_other = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg128UseAes(char* parameter) +{ + o.use_aes = (strcmp(parameter, "y") == 0); +} + +void +ArgParser::arg128ForceV4() +{ + o.force_V4 = true; +} + +void +ArgParser::arg256ForceR5() +{ + o.force_R5 = true; +} + +void +ArgParser::argEndEncrypt() +{ + o.encrypt = true; + o.decrypt = false; + o.copy_encryption = false; +} + +void +ArgParser::argUOpositional(char* arg) +{ + if (o.under_overlay->filename) + { + usage(o.under_overlay->which + " file already specified"); + } + else + { + o.under_overlay->filename = arg; + } +} + +void +ArgParser::argUOto(char* parameter) +{ + parseNumrange(parameter, 0); + o.under_overlay->to_nr = parameter; +} + +void +ArgParser::argUOfrom(char* parameter) +{ + if (strlen(parameter)) + { + parseNumrange(parameter, 0); + } + o.under_overlay->from_nr = parameter; +} + +void +ArgParser::argUOrepeat(char* parameter) +{ + if (strlen(parameter)) + { + parseNumrange(parameter, 0); + } + o.under_overlay->repeat_nr = parameter; +} + +void +ArgParser::argUOpassword(char* parameter) +{ + o.under_overlay->password = parameter; +} + +void +ArgParser::argEndUnderOverlay() +{ + if (0 == o.under_overlay->filename) + { + usage(o.under_overlay->which + " file not specified"); + } + o.under_overlay = 0; +} + +void +ArgParser::argReplaceInput() +{ + o.replace_input = true; +} + +void +ArgParser::argIsEncrypted() +{ + o.check_is_encrypted = true; + o.require_outfile = false; +} + +void +ArgParser::argRequiresPassword() +{ + o.check_requires_password = true; + o.require_outfile = false; +} + +void +ArgParser::argAApositional(char* arg) +{ + o.attachments_to_add.back().path = arg; +} + +void +ArgParser::argAAKey(char* parameter) +{ + o.attachments_to_add.back().key = parameter; +} + +void +ArgParser::argAAFilename(char* parameter) +{ + o.attachments_to_add.back().filename = parameter; +} + +void +ArgParser::argAACreationDate(char* parameter) +{ + if (! QUtil::pdf_time_to_qpdf_time(parameter)) + { + usage(std::string(parameter) + " is not a valid PDF timestamp"); + } + o.attachments_to_add.back().creationdate = parameter; +} + +void +ArgParser::argAAModDate(char* parameter) +{ + if (! QUtil::pdf_time_to_qpdf_time(parameter)) + { + usage(std::string(parameter) + " is not a valid PDF timestamp"); + } + o.attachments_to_add.back().moddate = parameter; +} + +void +ArgParser::argAAMimeType(char* parameter) +{ + if (strchr(parameter, '/') == nullptr) + { + usage("mime type should be specified as type/subtype"); + } + o.attachments_to_add.back().mimetype = parameter; +} + +void +ArgParser::argAADescription(char* parameter) +{ + o.attachments_to_add.back().description = parameter; +} + +void +ArgParser::argAAReplace() +{ + o.attachments_to_add.back().replace = true; +} + +void +ArgParser::argEndAddAttachment() +{ + static std::string now = QUtil::qpdf_time_to_pdf_time( + QUtil::get_current_qpdf_time()); + auto& cur = o.attachments_to_add.back(); + if (cur.path.empty()) + { + usage("add attachment: no path specified"); + } + std::string last_element = QUtil::path_basename(cur.path); + if (last_element.empty()) + { + usage("path for --add-attachment may not be empty"); + } + if (cur.filename.empty()) + { + cur.filename = last_element; + } + if (cur.key.empty()) + { + cur.key = last_element; + } + if (cur.creationdate.empty()) + { + cur.creationdate = now; + } + if (cur.moddate.empty()) + { + cur.moddate = now; + } +} + +void +ArgParser::argCApositional(char* arg) +{ + o.attachments_to_copy.back().path = arg; +} + +void +ArgParser::argCAprefix(char* parameter) +{ + o.attachments_to_copy.back().prefix = parameter; +} + +void +ArgParser::argCApassword(char* parameter) +{ + o.attachments_to_copy.back().password = parameter; +} + +void +ArgParser::argEndCopyAttachments() +{ + if (o.attachments_to_copy.back().path.empty()) + { + usage("copy attachments: no path specified"); + } +} + +void +ArgParser::usage(std::string const& message) +{ + if (this->ap.isCompleting()) + { + // This will cause bash to fall back to regular file completion. + exit(0); + } + else + { + throw QPDFArgParser::Usage(message); + } +} + +std::vector<int> +ArgParser::parseNumrange(char const* range, int max, bool throw_error) +{ + try + { + return QUtil::parse_numrange(range, max); + } + catch (std::runtime_error& e) + { + if (throw_error) + { + throw(e); + } + else + { + usage(e.what()); + } + } + return std::vector<int>(); +} + +void +ArgParser::parseUnderOverlayOptions(QPDFJob::UnderOverlay* uo) +{ + o.under_overlay = uo; + this->ap.selectOptionTable(O_UNDER_OVERLAY); +} + +void +ArgParser::parseRotationParameter(std::string const& parameter) +{ + std::string angle_str; + std::string range; + size_t colon = parameter.find(':'); + int relative = 0; + if (colon != std::string::npos) + { + if (colon > 0) + { + angle_str = parameter.substr(0, colon); + } + if (colon + 1 < parameter.length()) + { + range = parameter.substr(colon + 1); + } + } + else + { + angle_str = parameter; + } + if (angle_str.length() > 0) + { + char first = angle_str.at(0); + if ((first == '+') || (first == '-')) + { + relative = ((first == '+') ? 1 : -1); + angle_str = angle_str.substr(1); + } + else if (! QUtil::is_digit(angle_str.at(0))) + { + angle_str = ""; + } + } + if (range.empty()) + { + range = "1-z"; + } + bool range_valid = false; + try + { + parseNumrange(range.c_str(), 0, true); + range_valid = true; + } + catch (std::runtime_error const&) + { + // ignore + } + if (range_valid && + ((angle_str == "0") ||(angle_str == "90") || + (angle_str == "180") || (angle_str == "270"))) + { + int angle = QUtil::string_to_int(angle_str.c_str()); + if (relative == -1) + { + angle = -angle; + } + o.rotations[range] = QPDFJob::RotationSpec(angle, (relative != 0)); + } + else + { + usage("invalid parameter to rotate: " + parameter); + } +} + +void +ArgParser::parseOptions() +{ + try + { + this->ap.parseArgs(); + } + catch (QPDFArgParser::Usage& e) + { + usage(e.what()); + } +} + +void +ArgParser::doFinalChecks() +{ + if (o.replace_input) + { + if (o.outfilename) + { + usage("--replace-input may not be used when" + " an output file is specified"); + } + else if (o.split_pages) + { + usage("--split-pages may not be used with --replace-input"); + } + } + if (o.infilename == 0) + { + usage("an input file name is required"); + } + else if (o.require_outfile && (o.outfilename == 0) && (! o.replace_input)) + { + usage("an output file name is required; use - for standard output"); + } + else if ((! o.require_outfile) && + ((o.outfilename != 0) || o.replace_input)) + { + usage("no output file may be given for this option"); + } + 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) + { + usage("--requires-password and --is-encrypted may not be given" + " together"); + } + + if (o.encrypt && (! o.allow_insecure) && + (o.owner_password.empty() && + (! o.user_password.empty()) && + (o.keylen == 256))) + { + // Note that empty owner passwords for R < 5 are copied from + // the user password, so this lack of security is not an issue + // for those files. Also we are consider only the ability to + // open the file without a password to be insecure. We are not + // concerned about whether the viewer enforces security + // settings when the user and owner password match. + usage("A PDF with a non-empty user password and an empty owner" + " password encrypted with a 256-bit key is insecure as it" + " can be opened without a password. If you really want to" + " do this, you must also give the --allow-insecure option" + " before the -- that follows --encrypt."); + } + + if (o.require_outfile && o.outfilename && + (strcmp(o.outfilename, "-") == 0)) + { + if (o.split_pages) + { + usage("--split-pages may not be used when" + " writing to standard output"); + } + if (o.verbose) // QXXXQ + { + usage("--verbose may not be used when" + " writing to standard output"); + } + if (o.progress) + { + usage("--progress may not be used when" + " writing to standard output"); + } + } + + if ((! o.split_pages) && QUtil::same_file(o.infilename, o.outfilename)) + { + QTC::TC("qpdf", "qpdf same file error"); + usage("input file and output file are the same;" + " use --replace-input to intentionally overwrite the input file"); + } +} + +void +QPDFJob::initializeFromArgv(int argc, char* argv[], char const* progname_env) +{ + if (progname_env == nullptr) + { + progname_env = "QPDF_EXECUTABLE"; + } + // QPDFArgParser must stay in scope for the life of the QPDFJob + // object since it holds dynamic memory used for argv, which is + // pointed to by other member variables. + this->m->ap = new QPDFArgParser(argc, argv, progname_env); + setMessagePrefix(this->m->ap->getProgname()); + ArgParser ap(*this->m->ap, *this); + ap.parseOptions(); +} diff --git a/libqpdf/build.mk b/libqpdf/build.mk index 33fec274..4f4020e9 100644 --- a/libqpdf/build.mk +++ b/libqpdf/build.mk @@ -65,6 +65,7 @@ SRCS_libqpdf = \ libqpdf/QPDFFileSpecObjectHelper.cc \ libqpdf/QPDFFormFieldObjectHelper.cc \ libqpdf/QPDFJob.cc \ + libqpdf/QPDFJob_argv.cc \ libqpdf/QPDFMatrix.cc \ libqpdf/QPDFNameTreeObjectHelper.cc \ libqpdf/QPDFNumberTreeObjectHelper.cc \ diff --git a/qpdf/qpdf.cc b/qpdf/qpdf.cc index 202fb751..bb57e88b 100644 --- a/qpdf/qpdf.cc +++ b/qpdf/qpdf.cc @@ -1,22 +1,14 @@ -// QXXXQ update headers +#include <qpdf/QPDFJob.hh> +#include <qpdf/QTC.hh> +#include <qpdf/QUtil.hh> -#include <iostream> -#include <string.h> -#include <stdlib.h> -//#include <fcntl.h> #include <cstdio> -#include <ctype.h> -#include <memory> - -#include <qpdf/QUtil.hh> -#include <qpdf/QTC.hh> -#include <qpdf/QPDFCryptoProvider.hh> -#include <qpdf/QPDFArgParser.hh> -#include <qpdf/QPDFJob.hh> -#include <qpdf/QIntC.hh> +#include <cstdlib> +#include <cstring> +#include <iostream> static int constexpr EXIT_ERROR = 2; -static int EXIT_WARNING = 3; // may be changed to 0 at runtime +static int constexpr EXIT_WARNING = 3; // For is-encrypted and requires-password static int constexpr EXIT_IS_NOT_ENCRYPTED = 2; @@ -24,2373 +16,15 @@ static int constexpr EXIT_CORRECT_PASSWORD = 3; static char const* whoami = 0; -class ArgParser -{ - public: - ArgParser(int argc, char* argv[], QPDFJob& o); - void parseOptions(); - - private: - static constexpr char const* O_PAGES = "pages"; - static constexpr char const* O_ENCRYPT = "encryption"; - static constexpr char const* O_ENCRYPT_40 = "40-bit encryption"; - static constexpr char const* O_ENCRYPT_128 = "128-bit encryption"; - static constexpr char const* O_ENCRYPT_256 = "256-bit encryption"; - static constexpr char const* O_UNDER_OVERLAY = "underlay/overlay"; - static constexpr char const* O_ATTACHMENT = "attachment"; - static constexpr char const* O_COPY_ATTACHMENT = "copy attachment"; - - void argHelp(); - void argVersion(); - void argCopyright(); - void argJsonHelp(); - void argShowCrypto(); - void argPositional(char* arg); - void argPassword(char* parameter); - void argPasswordFile(char* parameter); - void argEmpty(); - void argLinearize(); - void argEncrypt(); - void argDecrypt(); - void argPasswordIsHexKey(); - void argAllowInsecure(); - void argAllowWeakCrypto(); - void argPasswordMode(char* parameter); - void argSuppressPasswordRecovery(); - void argCopyEncryption(char* parameter); - void argEncryptionFilePassword(char* parameter); - void argPages(); - void argPagesPassword(char* parameter); - void argPagesPositional(char* parameter); - void argEndPages(); - void argUnderlay(); - void argOverlay(); - void argRotate(char* parameter); - void argCollate(char* parameter); - void argFlattenRotation(); - void argListAttachments(); - void argShowAttachment(char* parameter); - void argRemoveAttachment(char* parameter); - void argAddAttachment(); - void argCopyAttachments(); - void argStreamData(char* parameter); - void argCompressStreams(char* parameter); - void argRecompressFlate(); - void argCompressionLevel(char* parameter); - void argDecodeLevel(char* parameter); - void argNormalizeContent(char* parameter); - void argSuppressRecovery(); - void argObjectStreams(char* parameter); - void argIgnoreXrefStreams(); - void argQdf(); - void argPreserveUnreferenced(); - void argPreserveUnreferencedResources(); - void argRemoveUnreferencedResources(char* parameter); - void argKeepFilesOpen(char* parameter); - void argKeepFilesOpenThreshold(char* parameter); - void argNewlineBeforeEndstream(); - void argLinearizePass1(char* parameter); - void argCoalesceContents(); - void argFlattenAnnotations(char* parameter); - void argGenerateAppearances(); - void argMinVersion(char* parameter); - void argForceVersion(char* parameter); - void argSplitPages(char* parameter); - void argVerbose(); - void argProgress(); - void argNoWarn(); - void argWarningExitZero(); - void argDeterministicId(); - void argStaticId(); - void argStaticAesIv(); - void argNoOriginalObjectIds(); - void argShowEncryption(); - void argShowEncryptionKey(); - void argCheckLinearization(); - void argShowLinearization(); - void argShowXref(); - void argShowObject(char* parameter); - void argRawStreamData(); - void argFilteredStreamData(); - void argShowNpages(); - void argShowPages(); - void argWithImages(); - void argJson(); - void argJsonKey(char* parameter); - void argJsonObject(char* parameter); - void argCheck(); - void argOptimizeImages(); - void argExternalizeInlineImages(); - void argKeepInlineImages(); - void argRemovePageLabels(); - void argOiMinWidth(char* parameter); - void argOiMinHeight(char* parameter); - void argOiMinArea(char* parameter); - void argIiMinBytes(char* parameter); - void arg40Print(char* parameter); - void arg40Modify(char* parameter); - void arg40Extract(char* parameter); - void arg40Annotate(char* parameter); - void arg128Accessibility(char* parameter); - void arg128Extract(char* parameter); - void arg128Print(char* parameter); - void arg128Modify(char* parameter); - void arg128ClearTextMetadata(); - void arg128Assemble(char* parameter); - void arg128Annotate(char* parameter); - void arg128Form(char* parameter); - void arg128ModOther(char* parameter); - void arg128UseAes(char* parameter); - void arg128ForceV4(); - void arg256ForceR5(); - void argEncryptPositional(char* arg); - void argEndEncrypt(); - void argUOpositional(char* arg); - void argUOto(char* parameter); - void argUOfrom(char* parameter); - void argUOrepeat(char* parameter); - void argUOpassword(char* parameter); - void argEndUnderOverlay(); - void argReplaceInput(); - void argIsEncrypted(); - void argRequiresPassword(); - void argAApositional(char* arg); - void argAAKey(char* parameter); - void argAAFilename(char* parameter); - void argAACreationDate(char* parameter); - void argAAModDate(char* parameter); - void argAAMimeType(char* parameter); - void argAADescription(char* parameter); - void argAAReplace(); - void argEndAddAttachment(); - void argCApositional(char* arg); - void argCAprefix(char* parameter); - void argCApassword(char* parameter); - void argEndCopyAttachments(); - - void usage(std::string const& message); - void initOptionTable(); - void doFinalChecks(); - void parseUnderOverlayOptions(QPDFJob::UnderOverlay*); - void parseRotationParameter(std::string const&); - std::vector<int> parseNumrange(char const* range, int max, - bool throw_error = false); - - QPDFArgParser ap; - QPDFJob& o; - std::vector<char*> accumulated_args; - char* pages_password; -}; - -ArgParser::ArgParser(int argc, char* argv[], QPDFJob& o) : - ap(argc, argv, "QPDF_EXECUTABLE"), - o(o), - pages_password(nullptr) -{ - initOptionTable(); -} - -void -ArgParser::initOptionTable() -{ - auto b = [this](void (ArgParser::*f)()) { - return QPDFArgParser::bindBare(f, this); - }; - auto p = [this](void (ArgParser::*f)(char *)) { - return QPDFArgParser::bindParam(f, this); - }; - - this->ap.addFinalCheck(b(&ArgParser::doFinalChecks)); - - this->ap.selectHelpOptionTable(); - this->ap.addBare("help", b(&ArgParser::argHelp)); - this->ap.addBare("version", b(&ArgParser::argVersion)); - this->ap.addBare("copyright", b(&ArgParser::argCopyright)); - this->ap.addBare("json-help", b(&ArgParser::argJsonHelp)); - this->ap.addBare("show-crypto", b(&ArgParser::argShowCrypto)); - - this->ap.selectMainOptionTable(); - char const* yn[] = {"y", "n", 0}; - this->ap.addPositional(p(&ArgParser::argPositional)); - this->ap.addRequiredParameter("password", - p(&ArgParser::argPassword), "password"); - this->ap.addRequiredParameter("password-file", - p(&ArgParser::argPasswordFile), "password-file"); - this->ap.addBare("empty", b(&ArgParser::argEmpty)); - this->ap.addBare("linearize", b(&ArgParser::argLinearize)); - this->ap.addBare("decrypt", b(&ArgParser::argDecrypt)); - this->ap.addBare("password-is-hex-key", b(&ArgParser::argPasswordIsHexKey)); - this->ap.addBare("suppress-password-recovery", - b(&ArgParser::argSuppressPasswordRecovery)); - char const* password_mode_choices[] = - {"bytes", "hex-bytes", "unicode", "auto", 0}; - this->ap.addRequiredChoices("password-mode", - p(&ArgParser::argPasswordMode), password_mode_choices); - this->ap.addRequiredParameter("copy-encryption", - p(&ArgParser::argCopyEncryption), "file"); - this->ap.addRequiredParameter("encryption-file-password", - p(&ArgParser::argEncryptionFilePassword), "password"); - this->ap.addRequiredParameter("rotate", - p(&ArgParser::argRotate), "[+|-]angle:page-range"); - char const* stream_data_choices[] = - {"compress", "preserve", "uncompress", 0}; - this->ap.addOptionalParameter("collate",p(&ArgParser::argCollate)); - this->ap.addBare("flatten-rotation", b(&ArgParser::argFlattenRotation)); - this->ap.addBare("list-attachments", b(&ArgParser::argListAttachments)); - this->ap.addRequiredParameter("show-attachment", - p(&ArgParser::argShowAttachment), "attachment-key"); - this->ap.addRequiredParameter("remove-attachment", - p(&ArgParser::argRemoveAttachment), "attachment-key"); - this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment)); - this->ap.addBare( - "copy-attachments-from", b(&ArgParser::argCopyAttachments)); - this->ap.addRequiredChoices("stream-data", - p(&ArgParser::argStreamData), stream_data_choices); - this->ap.addRequiredChoices("compress-streams", - p(&ArgParser::argCompressStreams), yn); - this->ap.addBare("recompress-flate", b(&ArgParser::argRecompressFlate)); - this->ap.addRequiredParameter("compression-level", - p(&ArgParser::argCompressionLevel), "level"); - char const* decode_level_choices[] = - {"none", "generalized", "specialized", "all", 0}; - this->ap.addRequiredChoices("decode-level", - p(&ArgParser::argDecodeLevel), decode_level_choices); - this->ap.addRequiredChoices("normalize-content", - p(&ArgParser::argNormalizeContent), yn); - this->ap.addBare("suppress-recovery", b(&ArgParser::argSuppressRecovery)); - char const* object_streams_choices[] = { - "disable", "preserve", "generate", 0}; - this->ap.addRequiredChoices("object-streams", - p(&ArgParser::argObjectStreams), object_streams_choices); - this->ap.addBare( - "ignore-xref-streams", b(&ArgParser::argIgnoreXrefStreams)); - this->ap.addBare("qdf", b(&ArgParser::argQdf)); - this->ap.addBare( - "preserve-unreferenced", b(&ArgParser::argPreserveUnreferenced)); - this->ap.addBare( - "preserve-unreferenced-resources", - b(&ArgParser::argPreserveUnreferencedResources)); - char const* remove_unref_choices[] = { - "auto", "yes", "no", 0}; - this->ap.addRequiredChoices("remove-unreferenced-resources", - p(&ArgParser::argRemoveUnreferencedResources), remove_unref_choices); - this->ap.addRequiredChoices("keep-files-open", - p(&ArgParser::argKeepFilesOpen), yn); - this->ap.addRequiredParameter("keep-files-open-threshold", - p(&ArgParser::argKeepFilesOpenThreshold), "count"); - this->ap.addBare("newline-before-endstream", b(&ArgParser::argNewlineBeforeEndstream)); - this->ap.addRequiredParameter("linearize-pass1", - p(&ArgParser::argLinearizePass1), "filename"); - this->ap.addBare("coalesce-contents", b(&ArgParser::argCoalesceContents)); - char const* flatten_choices[] = {"all", "print", "screen", 0}; - this->ap.addRequiredChoices("flatten-annotations", - p(&ArgParser::argFlattenAnnotations), flatten_choices); - this->ap.addBare("generate-appearances", b(&ArgParser::argGenerateAppearances)); - this->ap.addRequiredParameter("min-version", - p(&ArgParser::argMinVersion), "version"); - this->ap.addRequiredParameter("force-version", - p(&ArgParser::argForceVersion), "version"); - this->ap.addOptionalParameter("split-pages",p(&ArgParser::argSplitPages)); - this->ap.addBare("verbose", b(&ArgParser::argVerbose)); - this->ap.addBare("progress", b(&ArgParser::argProgress)); - this->ap.addBare("no-warn", b(&ArgParser::argNoWarn)); - this->ap.addBare("warning-exit-0", b(&ArgParser::argWarningExitZero)); - this->ap.addBare("deterministic-id", b(&ArgParser::argDeterministicId)); - this->ap.addBare("static-id", b(&ArgParser::argStaticId)); - this->ap.addBare("static-aes-iv", b(&ArgParser::argStaticAesIv)); - this->ap.addBare("no-original-object-ids", b(&ArgParser::argNoOriginalObjectIds)); - this->ap.addBare("show-encryption", b(&ArgParser::argShowEncryption)); - this->ap.addBare("show-encryption-key", b(&ArgParser::argShowEncryptionKey)); - this->ap.addBare("check-linearization", b(&ArgParser::argCheckLinearization)); - this->ap.addBare("show-linearization", b(&ArgParser::argShowLinearization)); - this->ap.addBare("show-xref", b(&ArgParser::argShowXref)); - this->ap.addRequiredParameter("show-object", - p(&ArgParser::argShowObject), "trailer|obj[,gen]"); - this->ap.addBare("raw-stream-data", b(&ArgParser::argRawStreamData)); - this->ap.addBare("filtered-stream-data", b(&ArgParser::argFilteredStreamData)); - this->ap.addBare("show-npages", b(&ArgParser::argShowNpages)); - 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[] = { - "objects", "objectinfo", "pages", "pagelabels", "outlines", - "acroform", "encrypt", "attachments", 0}; - this->ap.addRequiredChoices("json-key", - p(&ArgParser::argJsonKey), json_key_choices); - this->ap.addRequiredParameter("json-object", - p(&ArgParser::argJsonObject), "trailer|obj[,gen]"); - this->ap.addBare("check", b(&ArgParser::argCheck)); - this->ap.addBare("optimize-images", b(&ArgParser::argOptimizeImages)); - this->ap.addBare("externalize-inline-images", b(&ArgParser::argExternalizeInlineImages)); - this->ap.addBare("keep-inline-images", b(&ArgParser::argKeepInlineImages)); - this->ap.addBare("remove-page-labels", b(&ArgParser::argRemovePageLabels)); - this->ap.addRequiredParameter("oi-min-width", - p(&ArgParser::argOiMinWidth), "minimum-width"); - this->ap.addRequiredParameter("oi-min-height", - p(&ArgParser::argOiMinHeight), "minimum-height"); - this->ap.addRequiredParameter("oi-min-area", - p(&ArgParser::argOiMinArea), "minimum-area"); - this->ap.addRequiredParameter("ii-min-bytes", - p(&ArgParser::argIiMinBytes), "minimum-bytes"); - this->ap.addBare("overlay", b(&ArgParser::argOverlay)); - this->ap.addBare("underlay", b(&ArgParser::argUnderlay)); - this->ap.addBare("replace-input", b(&ArgParser::argReplaceInput)); - this->ap.addBare("is-encrypted", b(&ArgParser::argIsEncrypted)); - this->ap.addBare("requires-password", b(&ArgParser::argRequiresPassword)); - this->ap.addBare("allow-weak-crypto", b(&ArgParser::argAllowWeakCrypto)); - - this->ap.selectMainOptionTable(); - this->ap.addBare("pages", b(&ArgParser::argPages)); - this->ap.registerOptionTable(O_PAGES, b(&ArgParser::argEndPages)); - this->ap.addRequiredParameter( - "password", p(&ArgParser::argPagesPassword), "password"); - this->ap.addPositional(p(&ArgParser::argPagesPositional)); - - this->ap.selectMainOptionTable(); - this->ap.addBare("encrypt", b(&ArgParser::argEncrypt)); - this->ap.registerOptionTable(O_ENCRYPT, b(&ArgParser::argEndEncrypt)); - this->ap.addPositional(p(&ArgParser::argEncryptPositional)); - this->ap.registerOptionTable(O_ENCRYPT_40, b(&ArgParser::argEndEncrypt)); - this->ap.addRequiredChoices("extract",p(&ArgParser::arg40Extract), yn); - this->ap.addRequiredChoices("annotate",p(&ArgParser::arg40Annotate), yn); - this->ap.addRequiredChoices("print",p(&ArgParser::arg40Print), yn); - this->ap.addRequiredChoices("modify",p(&ArgParser::arg40Modify), yn); - this->ap.registerOptionTable(O_ENCRYPT_128, b(&ArgParser::argEndEncrypt)); - this->ap.registerOptionTable(O_ENCRYPT_256, b(&ArgParser::argEndEncrypt)); - for (char const* k: {O_ENCRYPT_128, O_ENCRYPT_256}) - { - this->ap.selectOptionTable(k); - this->ap.addRequiredChoices("accessibility", - p(&ArgParser::arg128Accessibility), yn); - this->ap.addRequiredChoices("extract", p(&ArgParser::arg128Extract), yn); - char const* print128_choices[] = {"full", "low", "none", 0}; - this->ap.addRequiredChoices("print", - p(&ArgParser::arg128Print), print128_choices); - this->ap.addRequiredChoices("assemble",p(&ArgParser::arg128Assemble), yn); - this->ap.addRequiredChoices("annotate",p(&ArgParser::arg128Annotate), yn); - this->ap.addRequiredChoices("form",p(&ArgParser::arg128Form), yn); - this->ap.addRequiredChoices("modify-other",p(&ArgParser::arg128ModOther), yn); - char const* modify128_choices[] = - {"all", "annotate", "form", "assembly", "none", 0}; - this->ap.addRequiredChoices("modify", - p(&ArgParser::arg128Modify), modify128_choices); - this->ap.addBare("cleartext-metadata", b(&ArgParser::arg128ClearTextMetadata)); - } - - this->ap.selectOptionTable(O_ENCRYPT_128); - this->ap.addRequiredChoices("use-aes",p(&ArgParser::arg128UseAes), yn); - this->ap.addBare("force-V4", b(&ArgParser::arg128ForceV4)); - - this->ap.selectOptionTable(O_ENCRYPT_256); - this->ap.addBare("force-R5", b(&ArgParser::arg256ForceR5)); - this->ap.addBare("allow-insecure", b(&ArgParser::argAllowInsecure)); - - this->ap.registerOptionTable(O_UNDER_OVERLAY, b(&ArgParser::argEndUnderOverlay)); - this->ap.addPositional(p(&ArgParser::argUOpositional)); - this->ap.addRequiredParameter("to", - p(&ArgParser::argUOto), "page-range"); - this->ap.addRequiredParameter("from", - p(&ArgParser::argUOfrom), "page-range"); - this->ap.addRequiredParameter("repeat", - p(&ArgParser::argUOrepeat), "page-range"); - this->ap.addRequiredParameter("password", - p(&ArgParser::argUOpassword), "password"); - - this->ap.registerOptionTable(O_ATTACHMENT, b(&ArgParser::argEndAddAttachment)); - this->ap.addPositional(p(&ArgParser::argAApositional)); - this->ap.addRequiredParameter("key", - p(&ArgParser::argAAKey), "attachment-key"); - this->ap.addRequiredParameter("filename", - p(&ArgParser::argAAFilename), "filename"); - this->ap.addRequiredParameter("creationdate", - p(&ArgParser::argAACreationDate), "creation-date"); - this->ap.addRequiredParameter("moddate", - p(&ArgParser::argAAModDate), "modification-date"); - this->ap.addRequiredParameter("mimetype", - p(&ArgParser::argAAMimeType), "mime/type"); - this->ap.addRequiredParameter("description", - p(&ArgParser::argAADescription), "description"); - this->ap.addBare("replace", b(&ArgParser::argAAReplace)); - - this->ap.registerOptionTable(O_COPY_ATTACHMENT, b(&ArgParser::argEndCopyAttachments)); - this->ap.addPositional(p(&ArgParser::argCApositional)); - this->ap.addRequiredParameter("prefix", - p(&ArgParser::argCAprefix), "prefix"); - this->ap.addRequiredParameter("password", - p(&ArgParser::argCApassword), "password"); -} - -void -ArgParser::argPositional(char* arg) -{ - if (o.infilename == 0) - { - o.infilename = arg; - } - else if (o.outfilename == 0) - { - o.outfilename = arg; - } - else - { - usage(std::string("unknown argument ") + arg); - } -} - -void -ArgParser::argVersion() -{ - std::cout - << whoami << " version " << QPDF::QPDFVersion() << std::endl - << "Run " << whoami << " --copyright to see copyright and license information." - << std::endl; -} - -void -ArgParser::argCopyright() -{ - // Make sure the output looks right on an 80-column display. - // 1 2 3 4 5 6 7 8 - // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 - std::cout - << whoami << " version " << QPDF::QPDFVersion() << std::endl - << std::endl - << "Copyright (c) 2005-2021 Jay Berkenbilt" - << std::endl - << "QPDF is licensed under the Apache License, Version 2.0 (the \"License\");" - << std::endl - << "you may not use this file except in compliance with the License." - << std::endl - << "You may obtain a copy of the License at" - << std::endl - << std::endl - << " http://www.apache.org/licenses/LICENSE-2.0" - << std::endl - << std::endl - << "Unless required by applicable law or agreed to in writing, software" - << std::endl - << "distributed under the License is distributed on an \"AS IS\" BASIS," - << std::endl - << "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." - << std::endl - << "See the License for the specific language governing permissions and" - << std::endl - << "limitations under the License." - << std::endl - << std::endl - << "Versions of qpdf prior to version 7 were released under the terms" - << std::endl - << "of version 2.0 of the Artistic License. At your option, you may" - << std::endl - << "continue to consider qpdf to be licensed under those terms. Please" - << std::endl - << "see the manual for additional information." - << std::endl; -} - -void -ArgParser::argHelp() -{ - std::cout - // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 - << "Usage: qpdf [options] {infile | --empty} [page_selection_options] outfile\n" - << "\n" - << "An option summary appears below. Please see the documentation for details.\n" - << "\n" - << "If @filename appears anywhere in the command-line, each line of filename\n" - << "will be interpreted as an argument. No interpolation is done. Line\n" - << "terminators are stripped, but leading and trailing whitespace is\n" - << "intentionally preserved. @- can be specified to read from standard input.\n" - << "\n" - << "The output file can be - to indicate writing to standard output, or it can\n" - << "be --replace-input to cause qpdf to replace the input file with the output.\n" - << "\n" - << "Note that when contradictory options are provided, whichever options are\n" - << "provided last take precedence.\n" - << "\n" - << "\n" - << "Basic Options\n" - << "-------------\n" - << "\n" - << "--version show version of qpdf\n" - << "--copyright show qpdf's copyright and license information\n" - << "--help show command-line argument help\n" - << "--show-crypto show supported crypto providers; default is first\n" - << "--completion-bash output a bash complete command you can eval\n" - << "--completion-zsh output a zsh complete command you can eval\n" - << "--password=password specify a password for accessing encrypted files\n" - << "--password-file=file get the password the first line \"file\"; use \"-\"\n" - << " to read the password from stdin (without prompt or\n" - << " disabling echo, so use with caution)\n" - << "--is-encrypted silently exit 0 if the file is encrypted or 2\n" - << " if not; useful for shell scripts\n" - << "--requires-password silently exit 0 if a password (other than as\n" - << " supplied) is required, 2 if the file is not\n" - << " encrypted, or 3 if the file is encrypted\n" - << " but requires no password or the supplied password\n" - << " is correct; useful for shell scripts\n" - << "--verbose provide additional informational output\n" - << "--progress give progress indicators while writing output\n" - << "--no-warn suppress warnings\n" - << "--warning-exit-0 exit with code 0 instead of 3 if there are warnings\n" - << "--linearize generated a linearized (web optimized) file\n" - << "--replace-input use in place of specifying an output file; qpdf will\n" - << " replace the input file with the output\n" - << "--copy-encryption=file copy encryption parameters from specified file\n" - << "--encryption-file-password=password\n" - << " password used to open the file from which encryption\n" - << " parameters are being copied\n" - << "--allow-weak-crypto allow creation of files using weak cryptographic\n" - << " algorithms\n" - << "--encrypt options -- generate an encrypted file\n" - << "--decrypt remove any encryption on the file\n" - << "--password-is-hex-key treat primary password option as a hex-encoded key\n" - << "--suppress-password-recovery\n" - << " do not attempt recovering from password string\n" - << " encoding errors\n" - << "--password-mode=mode control qpdf's encoding of passwords\n" - << "--pages options -- select specific pages from one or more files\n" - << "--collate=n causes files specified in --pages to be collated\n" - << " in groups of n pages (default 1) rather than\n" - << " concatenated\n" - << "--flatten-rotation move page rotation from /Rotate key to content\n" - << "--rotate=[+|-]angle[:page-range]\n" - << " rotate each specified page 0, 90, 180, or 270\n" - << " degrees; rotate all pages if no page range is given\n" - << "--split-pages=[n] write each output page to a separate file\n" - << "--overlay options -- overlay pages from another file\n" - << "--underlay options -- underlay pages from another file\n" - << "\n" - << "Note that you can use the @filename or @- syntax for any argument at any\n" - << "point in the command. This provides a good way to specify a password without\n" - << "having to explicitly put it on the command line. @filename or @- must be a\n" - << "word by itself. Syntax such as --arg=@filename doesn't work.\n" - << "\n" - << "If none of --copy-encryption, --encrypt or --decrypt are given, qpdf will\n" - << "preserve any encryption data associated with a file.\n" - << "\n" - << "Note that when copying encryption parameters from another file, all\n" - << "parameters will be copied, including both user and owner passwords, even\n" - << "if the user password is used to open the other file. This works even if\n" - << "the owner password is not known.\n" - << "\n" - << "The --password-is-hex-key option overrides the normal computation of\n" - << "encryption keys. It only applies to the password used to open the main\n" - << "file. This option is not ordinarily useful but can be helpful for forensic\n" - << "or investigatory purposes. See manual for further discussion.\n" - << "\n" - << "The --rotate flag can be used to specify pages to rotate pages either\n" - << "0, 90, 180, or 270 degrees. The page range is specified in the same\n" - << "format as with the --pages option, described below. Repeat the option\n" - << "to rotate multiple groups of pages. If the angle is preceded by + or -,\n" - << "it is added to or subtracted from the original rotation. Otherwise, the\n" - << "rotation angle is set explicitly to the given value. You almost always\n" - << "want to use + or - unless you are certain about the internals of the PDF\n" - << "you are working with.\n" - << "\n" - << "If --split-pages is specified, each page is written to a separate output\n" - << "file. File names are generated as follows:\n" - << "* If the string %d appears in the output file name, it is replaced with a\n" - << " zero-padded page range starting from 1\n" - << "* Otherwise, if the output file name ends in .pdf (case insensitive), a\n" - << " zero-padded page range, preceded by a dash, is inserted before the file\n" - << " extension\n" - << "* Otherwise, the file name is appended with a zero-padded page range\n" - << " preceded by a dash.\n" - << "Page ranges are single page numbers for single-page groups or first-last\n" - << "for multipage groups.\n" - << "\n" - << "\n" - << "Encryption Options\n" - << "------------------\n" - << "\n" - << " --encrypt user-password owner-password key-length flags --\n" - << "\n" - << "Note that -- terminates parsing of encryption flags.\n" - << "\n" - << "Either or both of the user password and the owner password may be\n" - << "empty strings.\n" - << "\n" - << "key-length may be 40, 128, or 256\n" - << "\n" - << "Additional flags are dependent upon key length.\n" - << "\n" - << " If 40:\n" - << "\n" - << " --print=[yn] allow printing\n" - << " --modify=[yn] allow document modification\n" - << " --extract=[yn] allow text/graphic extraction\n" - << " --annotate=[yn] allow comments and form fill-in and signing\n" - << "\n" - << " If 128:\n" - << "\n" - << " --accessibility=[yn] allow accessibility to visually impaired\n" - << " --extract=[yn] allow other text/graphic extraction\n" - << " --print=print-opt control printing access\n" - << " --assemble=[yn] allow document assembly\n" - << " --annotate=[yn] allow commenting/filling form fields\n" - << " --form=[yn] allow filling form fields\n" - << " --modify-other=[yn] allow other modifications\n" - << " --modify=modify-opt control modify access (old way)\n" - << " --cleartext-metadata prevents encryption of metadata\n" - << " --use-aes=[yn] indicates whether to use AES encryption\n" - << " --force-V4 forces use of V=4 encryption handler\n" - << "\n" - << " If 256, options are the same as 128 with these exceptions:\n" - << " --force-V4 this option is not available with 256-bit keys\n" - << " --use-aes this option is always on with 256-bit keys\n" - << " --force-R5 forces use of deprecated R=5 encryption\n" - << " --allow-insecure allow the owner password to be empty when the\n" - << " user password is not empty\n" - << "\n" - << " print-opt may be:\n" - << "\n" - << " full allow full printing\n" - << " low allow only low-resolution printing\n" - << " none disallow printing\n" - << "\n" - << " modify-opt may be:\n" - << "\n" - << " all allow full document modification\n" - << " annotate allow comment authoring and form operations\n" - << " form allow form field fill-in and signing\n" - << " assembly allow document assembly only\n" - << " none allow no modifications\n" - << "\n" - << "The default for each permission option is to be fully permissive. Please\n" - << "refer to the manual for more details on the modify options.\n" - << "\n" - << "Specifying cleartext-metadata forces the PDF version to at least 1.5.\n" - << "Specifying use of AES forces the PDF version to at least 1.6. These\n" - << "options are both off by default.\n" - << "\n" - << "The --force-V4 flag forces the V=4 encryption handler introduced in PDF 1.5\n" - << "to be used even if not otherwise needed. This option is primarily useful\n" - << "for testing qpdf and has no other practical use.\n" - << "\n" - << "A warning will be issued if you attempt to encrypt a file with a format that\n" - << "uses a weak cryptographic algorithm such as RC4. To suppress the warning,\n" - << "specify the option --allow-weak-crypto. This option is outside of encryption\n" - << "options (e.g. --allow-week-crypto --encrypt u o 128 --)\n" - << "\n" - << "\n" - << "Password Modes\n" - << "--------------\n" - << "\n" - << "The --password-mode controls how qpdf interprets passwords supplied\n" - << "on the command-line. qpdf's default behavior is correct in almost all\n" - << "cases, but you can fine-tune with this option.\n" - << "\n" - << " bytes: use the password literally as supplied\n" - << " hex-bytes: interpret the password as a hex-encoded byte string\n" - << " unicode: interpret the password as a UTF-8 encoded string\n" - << " auto: attempt to infer the encoding and adjust as needed\n" - << "\n" - << "This is a complex topic. See the manual for a complete discussion.\n" - << "\n" - << "\n" - << "Page Selection Options\n" - << "----------------------\n" - << "\n" - << "These options allow pages to be selected from one or more PDF files.\n" - << "Whatever file is given as the primary input file is used as the\n" - << "starting point, but its pages are replaced with pages as specified.\n" - << "\n" - << "--keep-files-open=[yn]\n" - << "--keep-files-open-threshold=count\n" - << "--pages file [ --password=password ] [ page-range ] ... --\n" - << "\n" - << "For each file that pages should be taken from, specify the file, a\n" - << "password needed to open the file (if any), and a page range. The\n" - << "password needs to be given only once per file. If any of the input\n" - << "files are the same as the primary input file or the file used to copy\n" - << "encryption parameters (if specified), you do not need to repeat the\n" - << "password here. The same file can be repeated multiple times. The\n" - << "filename \".\" may be used to refer to the current input file. All\n" - << "non-page data (info, outlines, page numbers, etc. are taken from the\n" - << "primary input file. To discard this, use --empty as the primary\n" - << "input.\n" - << "\n" - << "By default, when more than 200 distinct files are specified, qpdf will\n" - << "close each file when not being referenced. With 200 files or fewer, all\n" - << "files will be kept open at the same time. This behavior can be overridden\n" - << "by specifying --keep-files-open=[yn]. Closing and opening files can have\n" - << "very high overhead on certain file systems, especially networked file\n" - << "systems. The threshold of 200 can be modified with\n" - << "--keep-files-open-threshold\n" - << "\n" - << "The page range is a set of numbers separated by commas, ranges of\n" - << "numbers separated dashes, or combinations of those. The character\n" - << "\"z\" represents the last page. A number preceded by an \"r\" indicates\n" - << "to count from the end, so \"r3-r1\" would be the last three pages of the\n" - << "document. Pages can appear in any order. Ranges can appear with a\n" - << "high number followed by a low number, which causes the pages to appear in\n" - << "reverse. Numbers may be repeated. A page range may be appended with :odd\n" - << "to indicate odd pages in the selected range or :even to indicate even\n" - << "pages.\n" - << "\n" - << "If the page range is omitted, the range of 1-z is assumed. qpdf decides\n" - << "that the page range is omitted if the range argument is either -- or a\n" - << "valid file name and not a valid range.\n" - << "\n" - << "The usual behavior of --pages is to add all pages from the first file,\n" - << "then all pages from the second file, and so on. If the --collate option\n" - << "is specified, then pages are collated instead. In other words, qpdf takes\n" - << "the first page from the first file, the first page from the second file,\n" - << "and so on until it runs out of files; then it takes the second page from\n" - << "each file, etc. When a file runs out of pages, it is skipped until all\n" - << "specified pages are taken from all files.\n" - << "\n" - << "See the manual for examples and a discussion of additional subtleties.\n" - << "\n" - << "\n" - << "Overlay and Underlay Options\n" - << "----------------------------\n" - << "\n" - << "These options allow pages from another file to be overlaid or underlaid\n" - << "on the primary output. Overlaid pages are drawn on top of the destination\n" - << "page and may obscure the page. Underlaid pages are drawn below the\n" - << "destination page.\n" - << "\n" - << "{--overlay | --underlay } file\n" - " [ --password=password ]\n" - " [ --to=page-range ]\n" - " [ --from=[page-range] ]\n" - " [ --repeat=page-range ]\n" - " --\n" - << "\n" - << "For overlay and underlay, a file and optional password are specified, along\n" - << "with a series of optional page ranges. The default behavior is that each\n" - << "page of the overlay or underlay file is imposed on the corresponding page\n" - << "of the primary output until it runs out of pages, and any extra pages are\n" - << "ignored. The page range options all take page ranges in the same form as\n" - << "the --pages option. They have the following meanings:\n" - << "\n" - << " --to: the pages in the primary output to which overlay/underlay is\n" - << " applied\n" - << " --from: the pages from the overlay/underlay file that are used\n" - << " --repeat: pages from the overlay/underlay that are repeated after\n" - << " any \"from\" pages have been exhausted\n" - << "\n" - << "\n" - << "Embedded Files/Attachments Options\n" - << "----------------------------------\n" - << "\n" - << "These options can be used to work with embedded files, also known as\n" - << "attachments.\n" - << "\n" - << "--list-attachments show key and stream number for embedded files;\n" - << " combine with --verbose for more detailed information\n" - << "--show-attachment=key write the contents of the specified attachment to\n" - << " standard output as binary data\n" - << "--add-attachment file options --\n" - << " add or replace an attachment\n" - << "--remove-attachment=key remove the specified attachment; repeatable\n" - << "--copy-attachments-from file options --\n" - << " copy attachments from another file\n" - << "\n" - << "The \"key\" option is the unique name under which the attachment is registered\n" - << "within the PDF file. You can get this using the --list-attachments option. This\n" - << "is usually the same as the filename, but it doesn't have to be.\n" - << "\n" - << "Options for adding attachments:\n" - << "\n" - << " file path to the file to attach\n" - << " --key=key the name of this in the embedded files table;\n" - << " defaults to the last path element of file\n" - << " --filename=name the file name of the attachment; this is what is\n" - << " usually displayed to the user; defaults to the\n" - << " last path element of file\n" - << " --creationdate=date creation date in PDF format; defaults to the\n" - << " current time\n" - << " --moddate=date modification date in PDF format; defaults to the\n" - << " current time\n" - << " --mimetype=type/subtype mime type of attachment (e.g. application/pdf)\n" - << " --description=\"text\" attachment description\n" - << " --replace replace any existing attachment with the same key\n" - << "\n" - << "Options for copying attachments:\n" - << "\n" - << " file file whose attachments should be copied\n" - << " --password=password password to open the other file, if needed\n" - << " --prefix=prefix a prefix to insert in front of each key;\n" - << " required if needed to ensure each attachment\n" - << " has a unique key\n" - << "\n" - << "Date format: D:yyyymmddhhmmss<z> where <z> is either Z for UTC or a timezone\n" - << "offset in the form -hh'mm' or +hh'mm'.\n" - << "Examples: D:20210207161528-05'00', D:20210207211528Z\n" - << "\n" - << "\n" - << "Advanced Parsing Options\n" - << "------------------------\n" - << "\n" - << "These options control aspects of how qpdf reads PDF files. Mostly these are\n" - << "of use to people who are working with damaged files. There is little reason\n" - << "to use these options unless you are trying to solve specific problems.\n" - << "\n" - << "--suppress-recovery prevents qpdf from attempting to recover damaged files\n" - << "--ignore-xref-streams tells qpdf to ignore any cross-reference streams\n" - << "\n" - << "\n" - << "Advanced Transformation Options\n" - << "-------------------------------\n" - << "\n" - << "These transformation options control fine points of how qpdf creates\n" - << "the output file. Mostly these are of use only to people who are very\n" - << "familiar with the PDF file format or who are PDF developers.\n" - << "\n" - << "--stream-data=option controls transformation of stream data (below)\n" - << "--compress-streams=[yn] controls whether to compress streams on output\n" - << "--decode-level=option controls how to filter streams from the input\n" - << "--recompress-flate recompress streams already compressed with Flate\n" - << "--compression-level=n set zlib compression level; most effective with\n" - << " --recompress-flate --object-streams=generate\n" - << "--normalize-content=[yn] enables or disables normalization of content streams\n" - << "--object-streams=mode controls handing of object streams\n" - << "--preserve-unreferenced preserve unreferenced objects\n" - << "--remove-unreferenced-resources={auto,yes,no}\n" - << " whether to remove unreferenced page resources\n" - << "--preserve-unreferenced-resources\n" - << " synonym for --remove-unreferenced-resources=no\n" - << "--newline-before-endstream always put a newline before endstream\n" - << "--coalesce-contents force all pages' content to be a single stream\n" - << "--flatten-annotations=option\n" - << " incorporate rendering of annotations into page\n" - << " contents including those for interactive form\n" - << " fields; may also want --generate-appearances\n" - << "--generate-appearances generate appearance streams for form fields\n" - << "--optimize-images compress images with DCT (JPEG) when advantageous\n" - << "--oi-min-width=w do not optimize images whose width is below w;\n" - << " default is 128. Use 0 to mean no minimum\n" - << "--oi-min-height=h do not optimize images whose height is below h\n" - << " default is 128. Use 0 to mean no minimum\n" - << "--oi-min-area=a do not optimize images whose pixel count is below a\n" - << " default is 16,384. Use 0 to mean no minimum\n" - << "--externalize-inline-images convert inline images to regular images; by\n" - << " default, images of at least 1,024 bytes are\n" - << " externalized\n" - << "--ii-min-bytes=bytes specify minimum size of inline images to be\n" - << " converted to regular images\n" - << "--keep-inline-images exclude inline images from image optimization\n" - << "--remove-page-labels remove any page labels present in the output file\n" - << "--qdf turns on \"QDF mode\" (below)\n" - << "--linearize-pass1=file write intermediate pass of linearized file\n" - << " for debugging\n" - << "--min-version=version sets the minimum PDF version of the output file\n" - << "--force-version=version forces this to be the PDF version of the output file\n" - << "\n" - << "Options for --flatten-annotations are all, print, or screen. If the option\n" - << "is print, only annotations marked as print are included. If the option is\n" - << "screen, options marked as \"no view\" are excluded. Otherwise, annotations\n" - << "are flattened regardless of the presence of print or NoView flags. It is\n" - << "common for PDF files to have a flag set that appearance streams need to be\n" - << "regenerated. This happens when someone changes a form value with software\n" - << "that does not know how to render the new value. qpdf will not flatten form\n" - << "fields in files like this. If you get this warning, you have two choices:\n" - << "either use qpdf's --generate-appearances flag to tell qpdf to go ahead and\n" - << "regenerate appearances, or use some other tool to generate the appearances.\n" - << "qpdf does a pretty good job with most forms when only ASCII and \"Windows\n" - << "ANSI\" characters are used in form field values, but if your form fields\n" - << "contain other characters, rich text, or are other than left justified, you\n" - << "will get better results first saving with other software.\n" - << "\n" - << "Version numbers may be expressed as major.minor.extension-level, so 1.7.3\n" - << "means PDF version 1.7 at extension level 3.\n" - << "\n" - << "Values for stream data options:\n" - << "\n" - << " compress recompress stream data when possible (default)\n" - << " preserve leave all stream data as is\n" - << " uncompress uncompress stream data when possible\n" - << "\n" - << "Values for object stream mode:\n" - << "\n" - << " preserve preserve original object streams (default)\n" - << " disable don't write any object streams\n" - << " generate use object streams wherever possible\n" - << "\n" - << "When --compress-streams=n is specified, this overrides the default behavior\n" - << "of qpdf, which is to attempt compress uncompressed streams. Setting\n" - << "stream data mode to uncompress or preserve has the same effect.\n" - << "\n" - << "The --decode-level parameter may be set to one of the following values:\n" - << " none do not decode streams\n" - << " generalized decode streams compressed with generalized filters\n" - << " including LZW, Flate, and the ASCII encoding filters.\n" - << " specialized additionally decode streams with non-lossy specialized\n" - << " filters including RunLength\n" - << " all additionally decode streams with lossy filters\n" - << " including DCT (JPEG)\n" - << "\n" - << "In qdf mode, by default, content normalization is turned on, and the\n" - << "stream data mode is set to uncompress. QDF mode does not support\n" - << "linearized files. The --linearize flag disables qdf mode.\n" - << "\n" - << "Setting the minimum PDF version of the output file may raise the version\n" - << "but will never lower it. Forcing the PDF version of the output file may\n" - << "set the PDF version to a lower value than actually allowed by the file's\n" - << "contents. You should only do this if you have no other possible way to\n" - << "open the file or if you know that the file definitely doesn't include\n" - << "features not supported later versions.\n" - << "\n" - << "Testing, Inspection, and Debugging Options\n" - << "------------------------------------------\n" - << "\n" - << "These options can be useful for digging into PDF files or for use in\n" - << "automated test suites for software that uses the qpdf library.\n" - << "\n" - << "--deterministic-id generate deterministic /ID\n" - << "--static-id generate static /ID: FOR TESTING ONLY!\n" - << "--static-aes-iv use a static initialization vector for AES-CBC\n" - << " This is option is not secure! FOR TESTING ONLY!\n" - << "--no-original-object-ids suppress original object ID comments in qdf mode\n" - << "--show-encryption quickly show encryption parameters\n" - << "--show-encryption-key when showing encryption, reveal the actual key\n" - << "--check-linearization check file integrity and linearization status\n" - << "--show-linearization check and show all linearization data\n" - << "--show-xref show the contents of the cross-reference table\n" - << "--show-object=trailer|obj[,gen]\n" - << " show the contents of the given object\n" - << " --raw-stream-data show raw stream data instead of object contents\n" - << " --filtered-stream-data show filtered stream data instead of object contents\n" - << "--show-npages print the number of pages in the file\n" - << "--show-pages shows the object/generation number for each page\n" - << " --with-images also shows the object IDs for images on each page\n" - << "--check check file structure + encryption, linearization\n" - << "--json generate a json representation of the file\n" - << "--json-help describe the format of the json representation\n" - << "--json-key=key repeatable; prune json structure to include only\n" - << " specified keys. If absent, all keys are shown\n" - << "--json-object=trailer|[obj,gen]\n" - << " repeatable; include only specified objects in the\n" - << " \"objects\" section of the json. If absent, all\n" - << " objects are shown\n" - << "\n" - << "The json representation generated by qpdf is designed to facilitate\n" - << "processing of qpdf from other programming languages that have a hard\n" - << "time calling C++ APIs. Run qpdf --json-help for details on the format.\n" - << "The manual has more in-depth information about the json representation\n" - << "and certain compatibility guarantees that qpdf provides.\n" - << "\n" - << "The --raw-stream-data and --filtered-stream-data options are ignored\n" - << "unless --show-object is given. Either of these options will cause the\n" - << "stream data to be written to standard output.\n" - << "\n" - << "If --filtered-stream-data is given and --normalize-content=y is also\n" - << "given, qpdf will attempt to normalize the stream data as if it is a\n" - << "page content stream. This attempt will be made even if it is not a\n" - << "page content stream, in which case it will produce unusable results.\n" - << "\n" - << "Ordinarily, qpdf exits with a status of 0 on success or a status of 2\n" - << "if any errors occurred. If there were warnings but not errors, qpdf\n" - << "exits with a status of 3. If warnings would have been issued but --no-warn\n" - << "was given, an exit status of 3 is still used. If you want qpdf to exit\n" - << "with status 0 when there are warnings, use the --warning-exit-0 flag.\n" - << "When --no-warn and --warning-exit-0 are used together, the effect is for\n" - << "qpdf to completely ignore warnings. qpdf does not use exit status 1,\n" - << "since that is used by the shell if it can't execute qpdf.\n"; -} - -void -ArgParser::argJsonHelp() -{ - // Make sure the output looks right on an 80-column display. - // 1 2 3 4 5 6 7 8 - // 12345678901234567890123456789012345678901234567890123456789012345678901234567890 - std::cout - << "The json block below contains the same structure with the same keys as the" - << std::endl - << "json generated by qpdf. In the block below, the values are descriptions of" - << std::endl - << "the meanings of those entries. The specific contract guaranteed by qpdf in" - << std::endl - << "its json representation is explained in more detail in the manual. You can" - << std::endl - << "specify a subset of top-level keys when you invoke qpdf, but the \"version\"" - << std::endl - << "and \"parameters\" keys will always be present. Note that the \"encrypt\"" - << std::endl - << "key's values will be populated for non-encrypted files. Some values will" - << std::endl - << "be null, and others will have values that apply to unencrypted files." - << std::endl - << QPDFJob::json_schema().unparse() - << std::endl; -} - -void -ArgParser::argShowCrypto() -{ - auto crypto = QPDFCryptoProvider::getRegisteredImpls(); - std::string default_crypto = QPDFCryptoProvider::getDefaultProvider(); - std::cout << default_crypto << std::endl; - for (auto const& iter: crypto) - { - if (iter != default_crypto) - { - std::cout << iter << std::endl; - } - } -} - -void -ArgParser::argPassword(char* parameter) -{ - o.password = parameter; -} - -void -ArgParser::argPasswordFile(char* parameter) -{ - std::list<std::string> lines; - if (strcmp(parameter, "-") == 0) - { - QTC::TC("qpdf", "qpdf password stdin"); - lines = QUtil::read_lines_from_file(std::cin); - } - else - { - QTC::TC("qpdf", "qpdf password file"); - lines = QUtil::read_lines_from_file(parameter); - } - if (lines.size() >= 1) - { - // Make sure the memory for this stays in scope. - o.password_alloc = std::shared_ptr<char>( - QUtil::copy_string(lines.front().c_str()), - std::default_delete<char[]>()); - o.password = o.password_alloc.get(); - - if (lines.size() > 1) - { - std::cerr << whoami << ": WARNING: all but the first line of" - << " the password file are ignored" << std::endl; - } - } -} - -void -ArgParser::argEmpty() -{ - o.infilename = ""; -} - -void -ArgParser::argLinearize() -{ - o.linearize = true; -} - -void -ArgParser::argEncrypt() -{ - this->accumulated_args.clear(); - if (this->ap.isCompleting() && this->ap.argsLeft() == 0) - { - this->ap.insertCompletion("user-password"); - } - this->ap.selectOptionTable(O_ENCRYPT); -} - -void -ArgParser::argEncryptPositional(char* arg) -{ - this->accumulated_args.push_back(arg); - size_t n_args = this->accumulated_args.size(); - if (n_args < 3) - { - if (this->ap.isCompleting() && (this->ap.argsLeft() == 0)) - { - if (n_args == 1) - { - this->ap.insertCompletion("owner-password"); - } - else if (n_args == 2) - { - this->ap.insertCompletion("40"); - this->ap.insertCompletion("128"); - this->ap.insertCompletion("256"); - } - } - return; - } - o.user_password = this->accumulated_args.at(0); - o.owner_password = this->accumulated_args.at(1); - std::string len_str = this->accumulated_args.at(2); - if (len_str == "40") - { - o.keylen = 40; - this->ap.selectOptionTable(O_ENCRYPT_40); - } - else if (len_str == "128") - { - o.keylen = 128; - this->ap.selectOptionTable(O_ENCRYPT_128); - } - else if (len_str == "256") - { - o.keylen = 256; - o.use_aes = true; - this->ap.selectOptionTable(O_ENCRYPT_256); - } - else - { - usage("encryption key length must be 40, 128, or 256"); - } -} - -void -ArgParser::argDecrypt() -{ - o.decrypt = true; - o.encrypt = false; - o.copy_encryption = false; -} - -void -ArgParser::argPasswordIsHexKey() -{ - o.password_is_hex_key = true; -} - -void -ArgParser::argSuppressPasswordRecovery() -{ - o.suppress_password_recovery = true; -} - -void -ArgParser::argPasswordMode(char* parameter) -{ - if (strcmp(parameter, "bytes") == 0) - { - o.password_mode = QPDFJob::pm_bytes; - } - else if (strcmp(parameter, "hex-bytes") == 0) - { - o.password_mode = QPDFJob::pm_hex_bytes; - } - else if (strcmp(parameter, "unicode") == 0) - { - o.password_mode = QPDFJob::pm_unicode; - } - else if (strcmp(parameter, "auto") == 0) - { - o.password_mode = QPDFJob::pm_auto; - } - else - { - usage("invalid password-mode option"); - } -} - -void -ArgParser::argAllowInsecure() -{ - o.allow_insecure = true; -} - -void -ArgParser::argAllowWeakCrypto() -{ - o.allow_weak_crypto = true; -} - -void -ArgParser::argCopyEncryption(char* parameter) -{ - o.encryption_file = parameter; - o.copy_encryption = true; - o.encrypt = false; - o.decrypt = false; -} - -void -ArgParser::argEncryptionFilePassword(char* parameter) -{ - o.encryption_file_password = parameter; -} - -void -ArgParser::argCollate(char* parameter) -{ - auto n = ((parameter == 0) ? 1 : - QUtil::string_to_uint(parameter)); - o.collate = QIntC::to_size(n); -} - -void -ArgParser::argPages() -{ - if (! o.page_specs.empty()) - { - usage("the --pages may only be specified one time"); - } - this->accumulated_args.clear(); - this->ap.selectOptionTable(O_PAGES); -} - -void -ArgParser::argPagesPassword(char* parameter) -{ - if (this->pages_password != nullptr) - { - QTC::TC("qpdf", "qpdf duplicated pages password"); - usage("--password already specified for this file"); - } - if (this->accumulated_args.size() != 1) - { - QTC::TC("qpdf", "qpdf misplaced pages password"); - usage("in --pages, --password must immediately follow a file name"); - } - this->pages_password = parameter; -} - -void -ArgParser::argPagesPositional(char* arg) -{ - if (arg == nullptr) - { - if (this->accumulated_args.empty()) - { - return; - } - } - else - { - this->accumulated_args.push_back(arg); - } - - char const* file = this->accumulated_args.at(0); - char const* range = nullptr; - - size_t n_args = this->accumulated_args.size(); - if (n_args >= 2) - { - range = this->accumulated_args.at(1); - } - - // See if the user omitted the range entirely, in which case we - // assume "1-z". - char* next_file = nullptr; - if (range == nullptr) - { - if (arg == nullptr) - { - // The filename or password was the last argument - QTC::TC("qpdf", "qpdf pages range omitted at end", - this->pages_password == nullptr ? 0 : 1); - } - else - { - // We need to accumulate some more arguments - return; - } - } - else - { - try - { - parseNumrange(range, 0, true); - } - catch (std::runtime_error& e1) - { - // The range is invalid. Let's see if it's a file. - if (strcmp(range, ".") == 0) - { - // "." means the input file. - QTC::TC("qpdf", "qpdf pages range omitted with ."); - } - else if (QUtil::file_can_be_opened(range)) - { - QTC::TC("qpdf", "qpdf pages range omitted in middle"); - // Yup, it's a file. - } - else - { - // Give the range error - usage(e1.what()); - } - next_file = const_cast<char*>(range); - range = nullptr; - } - } - if (range == nullptr) - { - range = "1-z"; - } - o.page_specs.push_back(QPDFJob::PageSpec(file, this->pages_password, range)); - this->accumulated_args.clear(); - this->pages_password = nullptr; - if (next_file != nullptr) - { - this->accumulated_args.push_back(next_file); - } -} - -void -ArgParser::argEndPages() -{ - argPagesPositional(nullptr); - if (o.page_specs.empty()) - { - usage("--pages: no page specifications given"); - } -} - -void -ArgParser::argUnderlay() -{ - parseUnderOverlayOptions(&o.underlay); -} - -void -ArgParser::argOverlay() -{ - parseUnderOverlayOptions(&o.overlay); -} - -void -ArgParser::argRotate(char* parameter) -{ - parseRotationParameter(parameter); -} - -void -ArgParser::argFlattenRotation() -{ - o.flatten_rotation = true; -} - -void -ArgParser::argListAttachments() -{ - o.list_attachments = true; - o.require_outfile = false; -} - -void -ArgParser::argShowAttachment(char* parameter) -{ - o.attachment_to_show = parameter; - o.require_outfile = false; -} - -void -ArgParser::argRemoveAttachment(char* parameter) -{ - o.attachments_to_remove.push_back(parameter); -} - -void -ArgParser::argAddAttachment() -{ - o.attachments_to_add.push_back(QPDFJob::AddAttachment()); - this->ap.selectOptionTable(O_ATTACHMENT); -} - -void -ArgParser::argCopyAttachments() -{ - o.attachments_to_copy.push_back(QPDFJob::CopyAttachmentFrom()); - this->ap.selectOptionTable(O_COPY_ATTACHMENT); -} - -void -ArgParser::argStreamData(char* parameter) -{ - o.stream_data_set = true; - if (strcmp(parameter, "compress") == 0) - { - o.stream_data_mode = qpdf_s_compress; - } - else if (strcmp(parameter, "preserve") == 0) - { - o.stream_data_mode = qpdf_s_preserve; - } - else if (strcmp(parameter, "uncompress") == 0) - { - o.stream_data_mode = qpdf_s_uncompress; - } - else - { - // If this happens, it means streamDataChoices in - // ArgParser::initOptionTable is wrong. - usage("invalid stream-data option"); - } -} - -void -ArgParser::argCompressStreams(char* parameter) -{ - o.compress_streams_set = true; - o.compress_streams = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::argRecompressFlate() -{ - o.recompress_flate_set = true; - o.recompress_flate = true; -} - -void -ArgParser::argCompressionLevel(char* parameter) -{ - o.compression_level = QUtil::string_to_int(parameter); -} - -void -ArgParser::argDecodeLevel(char* parameter) -{ - o.decode_level_set = true; - if (strcmp(parameter, "none") == 0) - { - o.decode_level = qpdf_dl_none; - } - else if (strcmp(parameter, "generalized") == 0) - { - o.decode_level = qpdf_dl_generalized; - } - else if (strcmp(parameter, "specialized") == 0) - { - o.decode_level = qpdf_dl_specialized; - } - else if (strcmp(parameter, "all") == 0) - { - o.decode_level = qpdf_dl_all; - } - else - { - // If this happens, it means decodeLevelChoices in - // ArgParser::initOptionTable is wrong. - usage("invalid option"); - } -} - -void -ArgParser::argNormalizeContent(char* parameter) -{ - o.normalize_set = true; - o.normalize = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::argSuppressRecovery() -{ - o.suppress_recovery = true; -} - -void -ArgParser::argObjectStreams(char* parameter) -{ - o.object_stream_set = true; - if (strcmp(parameter, "disable") == 0) - { - o.object_stream_mode = qpdf_o_disable; - } - else if (strcmp(parameter, "preserve") == 0) - { - o.object_stream_mode = qpdf_o_preserve; - } - else if (strcmp(parameter, "generate") == 0) - { - o.object_stream_mode = qpdf_o_generate; - } - else - { - // If this happens, it means objectStreamsChoices in - // ArgParser::initOptionTable is wrong. - usage("invalid object stream mode"); - } -} - -void -ArgParser::argIgnoreXrefStreams() -{ - o.ignore_xref_streams = true; -} - -void -ArgParser::argQdf() -{ - o.qdf_mode = true; -} - -void -ArgParser::argPreserveUnreferenced() -{ - o.preserve_unreferenced_objects = true; -} - -void -ArgParser::argPreserveUnreferencedResources() -{ - o.remove_unreferenced_page_resources = QPDFJob::re_no; -} - -void -ArgParser::argRemoveUnreferencedResources(char* parameter) -{ - if (strcmp(parameter, "auto") == 0) - { - o.remove_unreferenced_page_resources = QPDFJob::re_auto; - } - else if (strcmp(parameter, "yes") == 0) - { - o.remove_unreferenced_page_resources = QPDFJob::re_yes; - } - else if (strcmp(parameter, "no") == 0) - { - o.remove_unreferenced_page_resources = QPDFJob::re_no; - } - else - { - // If this happens, it means remove_unref_choices in - // ArgParser::initOptionTable is wrong. - usage("invalid value for --remove-unreferenced-page-resources"); - } -} - -void -ArgParser::argKeepFilesOpen(char* parameter) -{ - o.keep_files_open_set = true; - o.keep_files_open = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::argKeepFilesOpenThreshold(char* parameter) -{ - o.keep_files_open_threshold = QUtil::string_to_uint(parameter); -} - -void -ArgParser::argNewlineBeforeEndstream() -{ - o.newline_before_endstream = true; -} - -void -ArgParser::argLinearizePass1(char* parameter) -{ - o.linearize_pass1 = parameter; -} - -void -ArgParser::argCoalesceContents() -{ - o.coalesce_contents = true; -} - -void -ArgParser::argFlattenAnnotations(char* parameter) -{ - o.flatten_annotations = true; - if (strcmp(parameter, "screen") == 0) - { - o.flatten_annotations_forbidden |= an_no_view; - } - else if (strcmp(parameter, "print") == 0) - { - o.flatten_annotations_required |= an_print; - } -} - -void -ArgParser::argGenerateAppearances() -{ - o.generate_appearances = true; -} - -void -ArgParser::argMinVersion(char* parameter) -{ - o.min_version = parameter; -} - -void -ArgParser::argForceVersion(char* parameter) -{ - o.force_version = parameter; -} - -void -ArgParser::argSplitPages(char* parameter) -{ - int n = ((parameter == 0) ? 1 : - QUtil::string_to_int(parameter)); - o.split_pages = n; -} - -void -ArgParser::argVerbose() -{ - o.verbose = true; -} - -void -ArgParser::argProgress() -{ - o.progress = true; -} - -void -ArgParser::argNoWarn() -{ - o.suppress_warnings = true; -} - -void -ArgParser::argWarningExitZero() -{ - ::EXIT_WARNING = 0; -} - -void -ArgParser::argDeterministicId() -{ - o.deterministic_id = true; -} - -void -ArgParser::argStaticId() -{ - o.static_id = true; -} - -void -ArgParser::argStaticAesIv() -{ - o.static_aes_iv = true; -} - -void -ArgParser::argNoOriginalObjectIds() -{ - o.suppress_original_object_id = true; -} - -void -ArgParser::argShowEncryption() -{ - o.show_encryption = true; - o.require_outfile = false; -} - -void -ArgParser::argShowEncryptionKey() -{ - o.show_encryption_key = true; -} - -void -ArgParser::argCheckLinearization() -{ - o.check_linearization = true; - o.require_outfile = false; -} - -void -ArgParser::argShowLinearization() -{ - o.show_linearization = true; - o.require_outfile = false; -} - -void -ArgParser::argShowXref() -{ - o.show_xref = true; - o.require_outfile = false; -} - -void -ArgParser::argShowObject(char* parameter) -{ - QPDFJob::parse_object_id(parameter, o.show_trailer, o.show_obj, o.show_gen); - o.require_outfile = false; -} - -void -ArgParser::argRawStreamData() -{ - o.show_raw_stream_data = true; -} - -void -ArgParser::argFilteredStreamData() -{ - o.show_filtered_stream_data = true; -} - -void -ArgParser::argShowNpages() -{ - o.show_npages = true; - o.require_outfile = false; -} - -void -ArgParser::argShowPages() -{ - o.show_pages = true; - o.require_outfile = false; -} - -void -ArgParser::argWithImages() -{ - o.show_page_images = true; -} - -void -ArgParser::argJson() -{ - o.json = true; - o.require_outfile = false; -} - -void -ArgParser::argJsonKey(char* parameter) -{ - o.json_keys.insert(parameter); -} - -void -ArgParser::argJsonObject(char* parameter) -{ - o.json_objects.insert(parameter); -} - -void -ArgParser::argCheck() -{ - o.check = true; - o.require_outfile = false; -} - -void -ArgParser::argOptimizeImages() -{ - o.optimize_images = true; -} - -void -ArgParser::argExternalizeInlineImages() -{ - o.externalize_inline_images = true; -} - -void -ArgParser::argKeepInlineImages() -{ - o.keep_inline_images = true; -} - -void -ArgParser::argRemovePageLabels() -{ - o.remove_page_labels = true; -} - -void -ArgParser::argOiMinWidth(char* parameter) -{ - o.oi_min_width = QUtil::string_to_uint(parameter); -} - -void -ArgParser::argOiMinHeight(char* parameter) -{ - o.oi_min_height = QUtil::string_to_uint(parameter); -} - -void -ArgParser::argOiMinArea(char* parameter) -{ - o.oi_min_area = QUtil::string_to_uint(parameter); -} - -void -ArgParser::argIiMinBytes(char* parameter) -{ - o.ii_min_bytes = QUtil::string_to_uint(parameter); -} - -void -ArgParser::arg40Print(char* parameter) -{ - o.r2_print = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg40Modify(char* parameter) -{ - o.r2_modify = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg40Extract(char* parameter) -{ - o.r2_extract = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg40Annotate(char* parameter) -{ - o.r2_annotate = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg128Accessibility(char* parameter) -{ - o.r3_accessibility = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg128Extract(char* parameter) -{ - o.r3_extract = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg128Print(char* parameter) -{ - if (strcmp(parameter, "full") == 0) - { - o.r3_print = qpdf_r3p_full; - } - else if (strcmp(parameter, "low") == 0) - { - o.r3_print = qpdf_r3p_low; - } - else if (strcmp(parameter, "none") == 0) - { - o.r3_print = qpdf_r3p_none; - } - else - { - usage("invalid print option"); - } -} - -void -ArgParser::arg128Modify(char* parameter) -{ - if (strcmp(parameter, "all") == 0) - { - o.r3_assemble = true; - o.r3_annotate_and_form = true; - o.r3_form_filling = true; - o.r3_modify_other = true; - } - else if (strcmp(parameter, "annotate") == 0) - { - o.r3_assemble = true; - o.r3_annotate_and_form = true; - o.r3_form_filling = true; - o.r3_modify_other = false; - } - else if (strcmp(parameter, "form") == 0) - { - o.r3_assemble = true; - o.r3_annotate_and_form = false; - o.r3_form_filling = true; - o.r3_modify_other = false; - } - else if (strcmp(parameter, "assembly") == 0) - { - o.r3_assemble = true; - o.r3_annotate_and_form = false; - o.r3_form_filling = false; - o.r3_modify_other = false; - } - else if (strcmp(parameter, "none") == 0) - { - o.r3_assemble = false; - o.r3_annotate_and_form = false; - o.r3_form_filling = false; - o.r3_modify_other = false; - } - else - { - usage("invalid modify option"); - } -} - -void -ArgParser::arg128ClearTextMetadata() -{ - o.cleartext_metadata = true; -} - -void -ArgParser::arg128Assemble(char* parameter) -{ - o.r3_assemble = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg128Annotate(char* parameter) -{ - o.r3_annotate_and_form = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg128Form(char* parameter) -{ - o.r3_form_filling = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg128ModOther(char* parameter) -{ - o.r3_modify_other = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg128UseAes(char* parameter) -{ - o.use_aes = (strcmp(parameter, "y") == 0); -} - -void -ArgParser::arg128ForceV4() -{ - o.force_V4 = true; -} - -void -ArgParser::arg256ForceR5() -{ - o.force_R5 = true; -} - -void -ArgParser::argEndEncrypt() -{ - o.encrypt = true; - o.decrypt = false; - o.copy_encryption = false; -} - -void -ArgParser::argUOpositional(char* arg) -{ - if (o.under_overlay->filename) - { - usage(o.under_overlay->which + " file already specified"); - } - else - { - o.under_overlay->filename = arg; - } -} - -void -ArgParser::argUOto(char* parameter) -{ - parseNumrange(parameter, 0); - o.under_overlay->to_nr = parameter; -} - -void -ArgParser::argUOfrom(char* parameter) -{ - if (strlen(parameter)) - { - parseNumrange(parameter, 0); - } - o.under_overlay->from_nr = parameter; -} - -void -ArgParser::argUOrepeat(char* parameter) -{ - if (strlen(parameter)) - { - parseNumrange(parameter, 0); - } - o.under_overlay->repeat_nr = parameter; -} - -void -ArgParser::argUOpassword(char* parameter) -{ - o.under_overlay->password = parameter; -} - -void -ArgParser::argEndUnderOverlay() -{ - if (0 == o.under_overlay->filename) - { - usage(o.under_overlay->which + " file not specified"); - } - o.under_overlay = 0; -} - -void -ArgParser::argReplaceInput() -{ - o.replace_input = true; -} - -void -ArgParser::argIsEncrypted() -{ - o.check_is_encrypted = true; - o.require_outfile = false; -} - -void -ArgParser::argRequiresPassword() -{ - o.check_requires_password = true; - o.require_outfile = false; -} - -void -ArgParser::argAApositional(char* arg) -{ - o.attachments_to_add.back().path = arg; -} - -void -ArgParser::argAAKey(char* parameter) -{ - o.attachments_to_add.back().key = parameter; -} - -void -ArgParser::argAAFilename(char* parameter) -{ - o.attachments_to_add.back().filename = parameter; -} - -void -ArgParser::argAACreationDate(char* parameter) -{ - if (! QUtil::pdf_time_to_qpdf_time(parameter)) - { - usage(std::string(parameter) + " is not a valid PDF timestamp"); - } - o.attachments_to_add.back().creationdate = parameter; -} - -void -ArgParser::argAAModDate(char* parameter) -{ - if (! QUtil::pdf_time_to_qpdf_time(parameter)) - { - usage(std::string(parameter) + " is not a valid PDF timestamp"); - } - o.attachments_to_add.back().moddate = parameter; -} - -void -ArgParser::argAAMimeType(char* parameter) -{ - if (strchr(parameter, '/') == nullptr) - { - usage("mime type should be specified as type/subtype"); - } - o.attachments_to_add.back().mimetype = parameter; -} - -void -ArgParser::argAADescription(char* parameter) -{ - o.attachments_to_add.back().description = parameter; -} - -void -ArgParser::argAAReplace() -{ - o.attachments_to_add.back().replace = true; -} - -void -ArgParser::argEndAddAttachment() -{ - static std::string now = QUtil::qpdf_time_to_pdf_time( - QUtil::get_current_qpdf_time()); - auto& cur = o.attachments_to_add.back(); - if (cur.path.empty()) - { - usage("add attachment: no path specified"); - } - std::string last_element = QUtil::path_basename(cur.path); - if (last_element.empty()) - { - usage("path for --add-attachment may not be empty"); - } - if (cur.filename.empty()) - { - cur.filename = last_element; - } - if (cur.key.empty()) - { - cur.key = last_element; - } - if (cur.creationdate.empty()) - { - cur.creationdate = now; - } - if (cur.moddate.empty()) - { - cur.moddate = now; - } -} - -void -ArgParser::argCApositional(char* arg) -{ - o.attachments_to_copy.back().path = arg; -} - -void -ArgParser::argCAprefix(char* parameter) -{ - o.attachments_to_copy.back().prefix = parameter; -} - -void -ArgParser::argCApassword(char* parameter) -{ - o.attachments_to_copy.back().password = parameter; -} - -void -ArgParser::argEndCopyAttachments() -{ - if (o.attachments_to_copy.back().path.empty()) - { - usage("copy attachments: no path specified"); - } -} - -void usageExit(std::string const& msg) +static void usageExit(std::string const& msg) { std::cerr << std::endl << whoami << ": " << msg << std::endl << std::endl - << "Usage: " << whoami << " [options] {infile | --empty} [page_selection_options] outfile" << std::endl << "For detailed help, run " << whoami << " --help" << std::endl << std::endl; - exit(EXIT_ERROR); // QXXXQ -} - -void -ArgParser::usage(std::string const& message) -{ - if (this->ap.isCompleting()) - { - // This will cause bash to fall back to regular file completion. - exit(0); - } - else - { - usageExit(message); - } -} - -std::vector<int> -ArgParser::parseNumrange(char const* range, int max, bool throw_error) -{ - try - { - return QUtil::parse_numrange(range, max); - } - catch (std::runtime_error& e) - { - if (throw_error) - { - throw(e); - } - else - { - usage(e.what()); - } - } - return std::vector<int>(); -} - -void -ArgParser::parseUnderOverlayOptions(QPDFJob::UnderOverlay* uo) -{ - o.under_overlay = uo; - this->ap.selectOptionTable(O_UNDER_OVERLAY); -} - -void -ArgParser::parseRotationParameter(std::string const& parameter) -{ - std::string angle_str; - std::string range; - size_t colon = parameter.find(':'); - int relative = 0; - if (colon != std::string::npos) - { - if (colon > 0) - { - angle_str = parameter.substr(0, colon); - } - if (colon + 1 < parameter.length()) - { - range = parameter.substr(colon + 1); - } - } - else - { - angle_str = parameter; - } - if (angle_str.length() > 0) - { - char first = angle_str.at(0); - if ((first == '+') || (first == '-')) - { - relative = ((first == '+') ? 1 : -1); - angle_str = angle_str.substr(1); - } - else if (! QUtil::is_digit(angle_str.at(0))) - { - angle_str = ""; - } - } - if (range.empty()) - { - range = "1-z"; - } - bool range_valid = false; - try - { - parseNumrange(range.c_str(), 0, true); - range_valid = true; - } - catch (std::runtime_error const&) - { - // ignore - } - if (range_valid && - ((angle_str == "0") ||(angle_str == "90") || - (angle_str == "180") || (angle_str == "270"))) - { - int angle = QUtil::string_to_int(angle_str.c_str()); - if (relative == -1) - { - angle = -angle; - } - o.rotations[range] = QPDFJob::RotationSpec(angle, (relative != 0)); - } - else - { - usage("invalid parameter to rotate: " + parameter); - } -} - -void -ArgParser::parseOptions() -{ - try - { - this->ap.parseArgs(); - } - catch (QPDFArgParser::Usage& e) - { - usage(e.what()); - } -} - -void -ArgParser::doFinalChecks() -{ - if (o.replace_input) - { - if (o.outfilename) - { - usage("--replace-input may not be used when" - " an output file is specified"); - } - else if (o.split_pages) - { - usage("--split-pages may not be used with --replace-input"); - } - } - if (o.infilename == 0) - { - usage("an input file name is required"); - } - else if (o.require_outfile && (o.outfilename == 0) && (! o.replace_input)) - { - usage("an output file name is required; use - for standard output"); - } - else if ((! o.require_outfile) && - ((o.outfilename != 0) || o.replace_input)) - { - usage("no output file may be given for this option"); - } - 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) - { - usage("--requires-password and --is-encrypted may not be given" - " together"); - } - - if (o.encrypt && (! o.allow_insecure) && - (o.owner_password.empty() && - (! o.user_password.empty()) && - (o.keylen == 256))) - { - // Note that empty owner passwords for R < 5 are copied from - // the user password, so this lack of security is not an issue - // for those files. Also we are consider only the ability to - // open the file without a password to be insecure. We are not - // concerned about whether the viewer enforces security - // settings when the user and owner password match. - usage("A PDF with a non-empty user password and an empty owner" - " password encrypted with a 256-bit key is insecure as it" - " can be opened without a password. If you really want to" - " do this, you must also give the --allow-insecure option" - " before the -- that follows --encrypt."); - } - - if (o.require_outfile && o.outfilename && - (strcmp(o.outfilename, "-") == 0)) - { - if (o.split_pages) - { - usage("--split-pages may not be used when" - " writing to standard output"); - } - if (o.verbose) // QXXXQ - { - usage("--verbose may not be used when" - " writing to standard output"); - } - if (o.progress) - { - usage("--progress may not be used when" - " writing to standard output"); - } - } - - if ((! o.split_pages) && QUtil::same_file(o.infilename, o.outfilename)) - { - QTC::TC("qpdf", "qpdf same file error"); - usage("input file and output file are the same;" - " use --replace-input to intentionally overwrite the input file"); - } + exit(EXIT_ERROR); } int realmain(int argc, char* argv[]) @@ -2404,27 +38,25 @@ int realmain(int argc, char* argv[]) whoami += 3; } - // 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; try { - ap.parseOptions(); + j.initializeFromArgv(argc, argv); j.run(); } + catch (QPDFArgParser::Usage& e) + { + usageExit(e.what()); + } catch (std::exception& e) { std::cerr << whoami << ": " << e.what() << std::endl; errors = true; } - // QXXXQ bool warnings = j.hasWarnings(); - if (warnings) { if (! j.suppressWarnings()) @@ -2442,7 +74,12 @@ int realmain(int argc, char* argv[]) } } // Still return with warning code even if warnings were - // suppressed, so leave warnings == true. + // suppressed, so leave warnings == true unless we've been + // specifically instructed to do otherwise. + if (j.warningsExitZero()) + { + warnings = false; + } } unsigned long encryption_status = j.getEncryptionStatus(); diff --git a/qpdf/qtest/qpdf/split-pages-stdout.out b/qpdf/qtest/qpdf/split-pages-stdout.out index 415ad2df..67dfdb6d 100644 --- a/qpdf/qtest/qpdf/split-pages-stdout.out +++ b/qpdf/qtest/qpdf/split-pages-stdout.out @@ -1,6 +1,5 @@ qpdf: --split-pages may not be used when writing to standard output -Usage: qpdf [options] {infile | --empty} [page_selection_options] outfile For detailed help, run qpdf --help |