Commit c60b4ea55a7d0844a81417d004f32c1b5f9f9df0

Authored by Jay Berkenbilt
1 parent 52817f0a

Refactor arg parsing in qpdf.cc to use QPDFArgParser

qpdf/qpdf.cc
... ... @@ -27,6 +27,7 @@
27 27 #include <qpdf/QPDFSystemError.hh>
28 28 #include <qpdf/QPDFCryptoProvider.hh>
29 29 #include <qpdf/QPDFEmbeddedFileDocumentHelper.hh>
  30 +#include <qpdf/QPDFArgParser.hh>
30 31  
31 32 #include <qpdf/QPDFWriter.hh>
32 33 #include <qpdf/QIntC.hh>
... ... @@ -740,14 +741,6 @@ static void parse_object_id(std::string const&amp; objspec,
740 741 }
741 742 }
742 743  
743   -// This is not a general-purpose argument parser. It is tightly
744   -// crafted to work with qpdf. qpdf's command-line syntax is very
745   -// complex because of its long history, and it doesn't really follow
746   -// any kind of normal standard for arguments, but I don't want to
747   -// break compatibility by changing what constitutes a valid command.
748   -// This class is intended to simplify the argument parsing code and
749   -// also to make it possible to add bash completion support while
750   -// guaranteeing consistency with the actual argument syntax.
751 744 class ArgParser
752 745 {
753 746 public:
... ... @@ -755,38 +748,18 @@ class ArgParser
755 748 void parseOptions();
756 749  
757 750 private:
758   - typedef void (ArgParser::*bare_arg_handler_t)();
759   - typedef void (ArgParser::*param_arg_handler_t)(char* parameter);
760   -
761   - struct OptionEntry
762   - {
763   - OptionEntry() :
764   - parameter_needed(false),
765   - bare_arg_handler(0),
766   - param_arg_handler(0)
767   - {
768   - }
769   - bool parameter_needed;
770   - std::string parameter_name;
771   - std::set<std::string> choices;
772   - bare_arg_handler_t bare_arg_handler;
773   - param_arg_handler_t param_arg_handler;
774   - };
775   - friend struct OptionEntry;
776   -
777   - OptionEntry oe_positional(param_arg_handler_t);
778   - OptionEntry oe_bare(bare_arg_handler_t);
779   - OptionEntry oe_requiredParameter(param_arg_handler_t, char const* name);
780   - OptionEntry oe_optionalParameter(param_arg_handler_t);
781   - OptionEntry oe_requiredChoices(param_arg_handler_t, char const** choices);
782   -
783   - void completionCommon(bool zsh);
  751 + static constexpr char const* O_PAGES = "pages";
  752 + static constexpr char const* O_ENCRYPT = "encryption";
  753 + static constexpr char const* O_ENCRYPT_40 = "40-bit encryption";
  754 + static constexpr char const* O_ENCRYPT_128 = "128-bit encryption";
  755 + static constexpr char const* O_ENCRYPT_256 = "256-bit encryption";
  756 + static constexpr char const* O_UNDER_OVERLAY = "underlay/overlay";
  757 + static constexpr char const* O_ATTACHMENT = "attachment";
  758 + static constexpr char const* O_COPY_ATTACHMENT = "copy attachment";
784 759  
785 760 void argHelp();
786 761 void argVersion();
787 762 void argCopyright();
788   - void argCompletionBash();
789   - void argCompletionZsh();
790 763 void argJsonHelp();
791 764 void argShowCrypto();
792 765 void argPositional(char* arg);
... ... @@ -804,6 +777,9 @@ class ArgParser
804 777 void argCopyEncryption(char* parameter);
805 778 void argEncryptionFilePassword(char* parameter);
806 779 void argPages();
  780 + void argPagesPassword(char* parameter);
  781 + void argPagesPositional(char* parameter);
  782 + void argEndPages();
807 783 void argUnderlay();
808 784 void argOverlay();
809 785 void argRotate(char* parameter);
... ... @@ -884,6 +860,7 @@ class ArgParser
884 860 void arg128UseAes(char* parameter);
885 861 void arg128ForceV4();
886 862 void arg256ForceR5();
  863 + void argEncryptPositional(char* arg);
887 864 void argEndEncrypt();
888 865 void argUOpositional(char* arg);
889 866 void argUOto(char* parameter);
... ... @@ -909,329 +886,257 @@ class ArgParser
909 886 void argEndCopyAttachments();
910 887  
911 888 void usage(std::string const& message);
912   - void checkCompletion();
913 889 void initOptionTable();
914   - void handleArgFileArguments();
915   - void handleBashArguments();
916   - void readArgsFromFile(char const* filename);
917 890 void doFinalChecks();
918   - void addOptionsToCompletions();
919   - void addChoicesToCompletions(std::string const&, std::string const&);
920   - void handleCompletion();
921   - std::vector<PageSpec> parsePagesOptions();
922 891 void parseUnderOverlayOptions(UnderOverlay*);
923 892 void parseRotationParameter(std::string const&);
924 893 std::vector<int> parseNumrange(char const* range, int max,
925 894 bool throw_error = false);
926 895  
927   - int argc;
928   - char** argv;
  896 + QPDFArgParser ap;
929 897 Options& o;
930   - int cur_arg;
931   - bool bash_completion;
932   - bool zsh_completion;
933   - std::string bash_prev;
934   - std::string bash_cur;
935   - std::string bash_line;
936   - std::set<std::string> completions;
937   -
938   - std::map<std::string, OptionEntry>* option_table;
939   - std::map<std::string, OptionEntry> help_option_table;
940   - std::map<std::string, OptionEntry> main_option_table;
941   - std::map<std::string, OptionEntry> encrypt40_option_table;
942   - std::map<std::string, OptionEntry> encrypt128_option_table;
943   - std::map<std::string, OptionEntry> encrypt256_option_table;
944   - std::map<std::string, OptionEntry> under_overlay_option_table;
945   - std::map<std::string, OptionEntry> add_attachment_option_table;
946   - std::map<std::string, OptionEntry> copy_attachments_option_table;
947   - std::vector<PointerHolder<char> > new_argv;
948   - std::vector<PointerHolder<char> > bash_argv;
949   - PointerHolder<char*> argv_ph;
950   - PointerHolder<char*> bash_argv_ph;
  898 + std::vector<char*> accumulated_args;
  899 + char* pages_password;
951 900 };
952 901  
953 902 ArgParser::ArgParser(int argc, char* argv[], Options& o) :
954   - argc(argc),
955   - argv(argv),
  903 + ap(argc, argv, "QPDF_EXECUTABLE"),
956 904 o(o),
957   - cur_arg(0),
958   - bash_completion(false),
959   - zsh_completion(false)
  905 + pages_password(nullptr)
960 906 {
961   - option_table = &main_option_table;
962 907 initOptionTable();
963 908 }
964 909  
965   -ArgParser::OptionEntry
966   -ArgParser::oe_positional(param_arg_handler_t h)
967   -{
968   - OptionEntry oe;
969   - oe.param_arg_handler = h;
970   - return oe;
971   -}
972   -
973   -ArgParser::OptionEntry
974   -ArgParser::oe_bare(bare_arg_handler_t h)
975   -{
976   - OptionEntry oe;
977   - oe.parameter_needed = false;
978   - oe.bare_arg_handler = h;
979   - return oe;
980   -}
981   -
982   -ArgParser::OptionEntry
983   -ArgParser::oe_requiredParameter(param_arg_handler_t h, char const* name)
984   -{
985   - OptionEntry oe;
986   - oe.parameter_needed = true;
987   - oe.parameter_name = name;
988   - oe.param_arg_handler = h;
989   - return oe;
990   -}
991   -
992   -ArgParser::OptionEntry
993   -ArgParser::oe_optionalParameter(param_arg_handler_t h)
994   -{
995   - OptionEntry oe;
996   - oe.parameter_needed = false;
997   - oe.param_arg_handler = h;
998   - return oe;
999   -}
1000   -
1001   -ArgParser::OptionEntry
1002   -ArgParser::oe_requiredChoices(param_arg_handler_t h, char const** choices)
1003   -{
1004   - OptionEntry oe;
1005   - oe.parameter_needed = true;
1006   - oe.param_arg_handler = h;
1007   - for (char const** i = choices; *i; ++i)
1008   - {
1009   - oe.choices.insert(*i);
1010   - }
1011   - return oe;
1012   -}
1013   -
1014 910 void
1015 911 ArgParser::initOptionTable()
1016 912 {
1017   - std::map<std::string, OptionEntry>* t = &this->help_option_table;
1018   - (*t)["help"] = oe_bare(&ArgParser::argHelp);
1019   - (*t)["version"] = oe_bare(&ArgParser::argVersion);
1020   - (*t)["copyright"] = oe_bare(&ArgParser::argCopyright);
1021   - (*t)["completion-bash"] = oe_bare(&ArgParser::argCompletionBash);
1022   - (*t)["completion-zsh"] = oe_bare(&ArgParser::argCompletionZsh);
1023   - (*t)["json-help"] = oe_bare(&ArgParser::argJsonHelp);
1024   - (*t)["show-crypto"] = oe_bare(&ArgParser::argShowCrypto);
  913 + auto b = [this](void (ArgParser::*f)()) {
  914 + return QPDFArgParser::bindBare(f, this);
  915 + };
  916 + auto p = [this](void (ArgParser::*f)(char *)) {
  917 + return QPDFArgParser::bindParam(f, this);
  918 + };
  919 +
  920 + this->ap.addFinalCheck(b(&ArgParser::doFinalChecks));
1025 921  
1026   - t = &this->main_option_table;
  922 + this->ap.selectHelpOptionTable();
  923 + this->ap.addBare("help", b(&ArgParser::argHelp));
  924 + this->ap.addBare("version", b(&ArgParser::argVersion));
  925 + this->ap.addBare("copyright", b(&ArgParser::argCopyright));
  926 + this->ap.addBare("json-help", b(&ArgParser::argJsonHelp));
  927 + this->ap.addBare("show-crypto", b(&ArgParser::argShowCrypto));
  928 +
  929 + this->ap.selectMainOptionTable();
1027 930 char const* yn[] = {"y", "n", 0};
1028   - (*t)[""] = oe_positional(&ArgParser::argPositional);
1029   - (*t)["password"] = oe_requiredParameter(
1030   - &ArgParser::argPassword, "password");
1031   - (*t)["password-file"] = oe_requiredParameter(
1032   - &ArgParser::argPasswordFile, "password-file");
1033   - (*t)["empty"] = oe_bare(&ArgParser::argEmpty);
1034   - (*t)["linearize"] = oe_bare(&ArgParser::argLinearize);
1035   - (*t)["encrypt"] = oe_bare(&ArgParser::argEncrypt);
1036   - (*t)["decrypt"] = oe_bare(&ArgParser::argDecrypt);
1037   - (*t)["password-is-hex-key"] = oe_bare(&ArgParser::argPasswordIsHexKey);
1038   - (*t)["suppress-password-recovery"] =
1039   - oe_bare(&ArgParser::argSuppressPasswordRecovery);
  931 + this->ap.addPositional(p(&ArgParser::argPositional));
  932 + this->ap.addRequiredParameter("password",
  933 + p(&ArgParser::argPassword), "password");
  934 + this->ap.addRequiredParameter("password-file",
  935 + p(&ArgParser::argPasswordFile), "password-file");
  936 + this->ap.addBare("empty", b(&ArgParser::argEmpty));
  937 + this->ap.addBare("linearize", b(&ArgParser::argLinearize));
  938 + this->ap.addBare("decrypt", b(&ArgParser::argDecrypt));
  939 + this->ap.addBare("password-is-hex-key", b(&ArgParser::argPasswordIsHexKey));
  940 + this->ap.addBare("suppress-password-recovery",
  941 + b(&ArgParser::argSuppressPasswordRecovery));
1040 942 char const* password_mode_choices[] =
1041 943 {"bytes", "hex-bytes", "unicode", "auto", 0};
1042   - (*t)["password-mode"] = oe_requiredChoices(
1043   - &ArgParser::argPasswordMode, password_mode_choices);
1044   - (*t)["copy-encryption"] = oe_requiredParameter(
1045   - &ArgParser::argCopyEncryption, "file");
1046   - (*t)["encryption-file-password"] = oe_requiredParameter(
1047   - &ArgParser::argEncryptionFilePassword, "password");
1048   - (*t)["pages"] = oe_bare(&ArgParser::argPages);
1049   - (*t)["rotate"] = oe_requiredParameter(
1050   - &ArgParser::argRotate, "[+|-]angle:page-range");
  944 + this->ap.addRequiredChoices("password-mode",
  945 + p(&ArgParser::argPasswordMode), password_mode_choices);
  946 + this->ap.addRequiredParameter("copy-encryption",
  947 + p(&ArgParser::argCopyEncryption), "file");
  948 + this->ap.addRequiredParameter("encryption-file-password",
  949 + p(&ArgParser::argEncryptionFilePassword), "password");
  950 + this->ap.addRequiredParameter("rotate",
  951 + p(&ArgParser::argRotate), "[+|-]angle:page-range");
1051 952 char const* stream_data_choices[] =
1052 953 {"compress", "preserve", "uncompress", 0};
1053   - (*t)["collate"] = oe_optionalParameter(&ArgParser::argCollate);
1054   - (*t)["flatten-rotation"] = oe_bare(&ArgParser::argFlattenRotation);
1055   - (*t)["list-attachments"] = oe_bare(&ArgParser::argListAttachments);
1056   - (*t)["show-attachment"] = oe_requiredParameter(
1057   - &ArgParser::argShowAttachment, "attachment-key");
1058   - (*t)["remove-attachment"] = oe_requiredParameter(
1059   - &ArgParser::argRemoveAttachment, "attachment-key");
1060   - (*t)["add-attachment"] = oe_bare(&ArgParser::argAddAttachment);
1061   - (*t)["copy-attachments-from"] = oe_bare(&ArgParser::argCopyAttachments);
1062   - (*t)["stream-data"] = oe_requiredChoices(
1063   - &ArgParser::argStreamData, stream_data_choices);
1064   - (*t)["compress-streams"] = oe_requiredChoices(
1065   - &ArgParser::argCompressStreams, yn);
1066   - (*t)["recompress-flate"] = oe_bare(&ArgParser::argRecompressFlate);
1067   - (*t)["compression-level"] = oe_requiredParameter(
1068   - &ArgParser::argCompressionLevel, "level");
  954 + this->ap.addOptionalParameter("collate",p(&ArgParser::argCollate));
  955 + this->ap.addBare("flatten-rotation", b(&ArgParser::argFlattenRotation));
  956 + this->ap.addBare("list-attachments", b(&ArgParser::argListAttachments));
  957 + this->ap.addRequiredParameter("show-attachment",
  958 + p(&ArgParser::argShowAttachment), "attachment-key");
  959 + this->ap.addRequiredParameter("remove-attachment",
  960 + p(&ArgParser::argRemoveAttachment), "attachment-key");
  961 + this->ap.addBare("add-attachment", b(&ArgParser::argAddAttachment));
  962 + this->ap.addBare(
  963 + "copy-attachments-from", b(&ArgParser::argCopyAttachments));
  964 + this->ap.addRequiredChoices("stream-data",
  965 + p(&ArgParser::argStreamData), stream_data_choices);
  966 + this->ap.addRequiredChoices("compress-streams",
  967 + p(&ArgParser::argCompressStreams), yn);
  968 + this->ap.addBare("recompress-flate", b(&ArgParser::argRecompressFlate));
  969 + this->ap.addRequiredParameter("compression-level",
  970 + p(&ArgParser::argCompressionLevel), "level");
1069 971 char const* decode_level_choices[] =
1070 972 {"none", "generalized", "specialized", "all", 0};
1071   - (*t)["decode-level"] = oe_requiredChoices(
1072   - &ArgParser::argDecodeLevel, decode_level_choices);
1073   - (*t)["normalize-content"] = oe_requiredChoices(
1074   - &ArgParser::argNormalizeContent, yn);
1075   - (*t)["suppress-recovery"] = oe_bare(&ArgParser::argSuppressRecovery);
  973 + this->ap.addRequiredChoices("decode-level",
  974 + p(&ArgParser::argDecodeLevel), decode_level_choices);
  975 + this->ap.addRequiredChoices("normalize-content",
  976 + p(&ArgParser::argNormalizeContent), yn);
  977 + this->ap.addBare("suppress-recovery", b(&ArgParser::argSuppressRecovery));
1076 978 char const* object_streams_choices[] = {
1077 979 "disable", "preserve", "generate", 0};
1078   - (*t)["object-streams"] = oe_requiredChoices(
1079   - &ArgParser::argObjectStreams, object_streams_choices);
1080   - (*t)["ignore-xref-streams"] = oe_bare(&ArgParser::argIgnoreXrefStreams);
1081   - (*t)["qdf"] = oe_bare(&ArgParser::argQdf);
1082   - (*t)["preserve-unreferenced"] = oe_bare(
1083   - &ArgParser::argPreserveUnreferenced);
1084   - (*t)["preserve-unreferenced-resources"] = oe_bare(
1085   - &ArgParser::argPreserveUnreferencedResources);
  980 + this->ap.addRequiredChoices("object-streams",
  981 + p(&ArgParser::argObjectStreams), object_streams_choices);
  982 + this->ap.addBare(
  983 + "ignore-xref-streams", b(&ArgParser::argIgnoreXrefStreams));
  984 + this->ap.addBare("qdf", b(&ArgParser::argQdf));
  985 + this->ap.addBare(
  986 + "preserve-unreferenced", b(&ArgParser::argPreserveUnreferenced));
  987 + this->ap.addBare(
  988 + "preserve-unreferenced-resources",
  989 + b(&ArgParser::argPreserveUnreferencedResources));
1086 990 char const* remove_unref_choices[] = {
1087 991 "auto", "yes", "no", 0};
1088   - (*t)["remove-unreferenced-resources"] = oe_requiredChoices(
1089   - &ArgParser::argRemoveUnreferencedResources, remove_unref_choices);
1090   - (*t)["keep-files-open"] = oe_requiredChoices(
1091   - &ArgParser::argKeepFilesOpen, yn);
1092   - (*t)["keep-files-open-threshold"] = oe_requiredParameter(
1093   - &ArgParser::argKeepFilesOpenThreshold, "count");
1094   - (*t)["newline-before-endstream"] = oe_bare(
1095   - &ArgParser::argNewlineBeforeEndstream);
1096   - (*t)["linearize-pass1"] = oe_requiredParameter(
1097   - &ArgParser::argLinearizePass1, "filename");
1098   - (*t)["coalesce-contents"] = oe_bare(&ArgParser::argCoalesceContents);
  992 + this->ap.addRequiredChoices("remove-unreferenced-resources",
  993 + p(&ArgParser::argRemoveUnreferencedResources), remove_unref_choices);
  994 + this->ap.addRequiredChoices("keep-files-open",
  995 + p(&ArgParser::argKeepFilesOpen), yn);
  996 + this->ap.addRequiredParameter("keep-files-open-threshold",
  997 + p(&ArgParser::argKeepFilesOpenThreshold), "count");
  998 + this->ap.addBare("newline-before-endstream", b(&ArgParser::argNewlineBeforeEndstream));
  999 + this->ap.addRequiredParameter("linearize-pass1",
  1000 + p(&ArgParser::argLinearizePass1), "filename");
  1001 + this->ap.addBare("coalesce-contents", b(&ArgParser::argCoalesceContents));
1099 1002 char const* flatten_choices[] = {"all", "print", "screen", 0};
1100   - (*t)["flatten-annotations"] = oe_requiredChoices(
1101   - &ArgParser::argFlattenAnnotations, flatten_choices);
1102   - (*t)["generate-appearances"] =
1103   - oe_bare(&ArgParser::argGenerateAppearances);
1104   - (*t)["min-version"] = oe_requiredParameter(
1105   - &ArgParser::argMinVersion, "version");
1106   - (*t)["force-version"] = oe_requiredParameter(
1107   - &ArgParser::argForceVersion, "version");
1108   - (*t)["split-pages"] = oe_optionalParameter(&ArgParser::argSplitPages);
1109   - (*t)["verbose"] = oe_bare(&ArgParser::argVerbose);
1110   - (*t)["progress"] = oe_bare(&ArgParser::argProgress);
1111   - (*t)["no-warn"] = oe_bare(&ArgParser::argNoWarn);
1112   - (*t)["warning-exit-0"] = oe_bare(&ArgParser::argWarningExitZero);
1113   - (*t)["deterministic-id"] = oe_bare(&ArgParser::argDeterministicId);
1114   - (*t)["static-id"] = oe_bare(&ArgParser::argStaticId);
1115   - (*t)["static-aes-iv"] = oe_bare(&ArgParser::argStaticAesIv);
1116   - (*t)["no-original-object-ids"] = oe_bare(
1117   - &ArgParser::argNoOriginalObjectIds);
1118   - (*t)["show-encryption"] = oe_bare(&ArgParser::argShowEncryption);
1119   - (*t)["show-encryption-key"] = oe_bare(&ArgParser::argShowEncryptionKey);
1120   - (*t)["check-linearization"] = oe_bare(&ArgParser::argCheckLinearization);
1121   - (*t)["show-linearization"] = oe_bare(&ArgParser::argShowLinearization);
1122   - (*t)["show-xref"] = oe_bare(&ArgParser::argShowXref);
1123   - (*t)["show-object"] = oe_requiredParameter(
1124   - &ArgParser::argShowObject, "trailer|obj[,gen]");
1125   - (*t)["raw-stream-data"] = oe_bare(&ArgParser::argRawStreamData);
1126   - (*t)["filtered-stream-data"] = oe_bare(&ArgParser::argFilteredStreamData);
1127   - (*t)["show-npages"] = oe_bare(&ArgParser::argShowNpages);
1128   - (*t)["show-pages"] = oe_bare(&ArgParser::argShowPages);
1129   - (*t)["with-images"] = oe_bare(&ArgParser::argWithImages);
1130   - (*t)["json"] = oe_bare(&ArgParser::argJson);
  1003 + this->ap.addRequiredChoices("flatten-annotations",
  1004 + p(&ArgParser::argFlattenAnnotations), flatten_choices);
  1005 + this->ap.addBare("generate-appearances", b(&ArgParser::argGenerateAppearances));
  1006 + this->ap.addRequiredParameter("min-version",
  1007 + p(&ArgParser::argMinVersion), "version");
  1008 + this->ap.addRequiredParameter("force-version",
  1009 + p(&ArgParser::argForceVersion), "version");
  1010 + this->ap.addOptionalParameter("split-pages",p(&ArgParser::argSplitPages));
  1011 + this->ap.addBare("verbose", b(&ArgParser::argVerbose));
  1012 + this->ap.addBare("progress", b(&ArgParser::argProgress));
  1013 + this->ap.addBare("no-warn", b(&ArgParser::argNoWarn));
  1014 + this->ap.addBare("warning-exit-0", b(&ArgParser::argWarningExitZero));
  1015 + this->ap.addBare("deterministic-id", b(&ArgParser::argDeterministicId));
  1016 + this->ap.addBare("static-id", b(&ArgParser::argStaticId));
  1017 + this->ap.addBare("static-aes-iv", b(&ArgParser::argStaticAesIv));
  1018 + this->ap.addBare("no-original-object-ids", b(&ArgParser::argNoOriginalObjectIds));
  1019 + this->ap.addBare("show-encryption", b(&ArgParser::argShowEncryption));
  1020 + this->ap.addBare("show-encryption-key", b(&ArgParser::argShowEncryptionKey));
  1021 + this->ap.addBare("check-linearization", b(&ArgParser::argCheckLinearization));
  1022 + this->ap.addBare("show-linearization", b(&ArgParser::argShowLinearization));
  1023 + this->ap.addBare("show-xref", b(&ArgParser::argShowXref));
  1024 + this->ap.addRequiredParameter("show-object",
  1025 + p(&ArgParser::argShowObject), "trailer|obj[,gen]");
  1026 + this->ap.addBare("raw-stream-data", b(&ArgParser::argRawStreamData));
  1027 + this->ap.addBare("filtered-stream-data", b(&ArgParser::argFilteredStreamData));
  1028 + this->ap.addBare("show-npages", b(&ArgParser::argShowNpages));
  1029 + this->ap.addBare("show-pages", b(&ArgParser::argShowPages));
  1030 + this->ap.addBare("with-images", b(&ArgParser::argWithImages));
  1031 + this->ap.addBare("json", b(&ArgParser::argJson));
1131 1032 // The list of selectable top-level keys id duplicated in three
1132 1033 // places: json_schema, do_json, and initOptionTable.
1133 1034 char const* json_key_choices[] = {
1134 1035 "objects", "objectinfo", "pages", "pagelabels", "outlines",
1135 1036 "acroform", "encrypt", "attachments", 0};
1136   - (*t)["json-key"] = oe_requiredChoices(
1137   - &ArgParser::argJsonKey, json_key_choices);
1138   - (*t)["json-object"] = oe_requiredParameter(
1139   - &ArgParser::argJsonObject, "trailer|obj[,gen]");
1140   - (*t)["check"] = oe_bare(&ArgParser::argCheck);
1141   - (*t)["optimize-images"] = oe_bare(&ArgParser::argOptimizeImages);
1142   - (*t)["externalize-inline-images"] =
1143   - oe_bare(&ArgParser::argExternalizeInlineImages);
1144   - (*t)["keep-inline-images"] = oe_bare(&ArgParser::argKeepInlineImages);
1145   - (*t)["remove-page-labels"] = oe_bare(&ArgParser::argRemovePageLabels);
1146   - (*t)["oi-min-width"] = oe_requiredParameter(
1147   - &ArgParser::argOiMinWidth, "minimum-width");
1148   - (*t)["oi-min-height"] = oe_requiredParameter(
1149   - &ArgParser::argOiMinHeight, "minimum-height");
1150   - (*t)["oi-min-area"] = oe_requiredParameter(
1151   - &ArgParser::argOiMinArea, "minimum-area");
1152   - (*t)["ii-min-bytes"] = oe_requiredParameter(
1153   - &ArgParser::argIiMinBytes, "minimum-bytes");
1154   - (*t)["overlay"] = oe_bare(&ArgParser::argOverlay);
1155   - (*t)["underlay"] = oe_bare(&ArgParser::argUnderlay);
1156   - (*t)["replace-input"] = oe_bare(&ArgParser::argReplaceInput);
1157   - (*t)["is-encrypted"] = oe_bare(&ArgParser::argIsEncrypted);
1158   - (*t)["requires-password"] = oe_bare(&ArgParser::argRequiresPassword);
1159   - (*t)["allow-weak-crypto"] = oe_bare(&ArgParser::argAllowWeakCrypto);
1160   -
1161   - t = &this->encrypt40_option_table;
1162   - (*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
1163   - // The above 40-bit options are also 128-bit and 256-bit options,
1164   - // so copy what we have so far to 128. Then continue separately
1165   - // with 128. We later copy 128 to 256.
1166   - this->encrypt128_option_table = this->encrypt40_option_table;
1167   - (*t)["print"] = oe_requiredChoices(&ArgParser::arg40Print, yn);
1168   - (*t)["modify"] = oe_requiredChoices(&ArgParser::arg40Modify, yn);
1169   - (*t)["extract"] = oe_requiredChoices(&ArgParser::arg40Extract, yn);
1170   - (*t)["annotate"] = oe_requiredChoices(&ArgParser::arg40Annotate, yn);
1171   -
1172   - t = &this->encrypt128_option_table;
1173   - (*t)["accessibility"] = oe_requiredChoices(
1174   - &ArgParser::arg128Accessibility, yn);
1175   - (*t)["extract"] = oe_requiredChoices(&ArgParser::arg128Extract, yn);
1176   - char const* print128_choices[] = {"full", "low", "none", 0};
1177   - (*t)["print"] = oe_requiredChoices(
1178   - &ArgParser::arg128Print, print128_choices);
1179   - (*t)["assemble"] = oe_requiredChoices(&ArgParser::arg128Assemble, yn);
1180   - (*t)["annotate"] = oe_requiredChoices(&ArgParser::arg128Annotate, yn);
1181   - (*t)["form"] = oe_requiredChoices(&ArgParser::arg128Form, yn);
1182   - (*t)["modify-other"] = oe_requiredChoices(&ArgParser::arg128ModOther, yn);
1183   - char const* modify128_choices[] =
1184   - {"all", "annotate", "form", "assembly", "none", 0};
1185   - (*t)["modify"] = oe_requiredChoices(
1186   - &ArgParser::arg128Modify, modify128_choices);
1187   - (*t)["cleartext-metadata"] = oe_bare(&ArgParser::arg128ClearTextMetadata);
1188   -
1189   - // The above 128-bit options are also 256-bit options, so copy
1190   - // what we have so far. Then continue separately with 128 and 256.
1191   - this->encrypt256_option_table = this->encrypt128_option_table;
1192   - (*t)["use-aes"] = oe_requiredChoices(&ArgParser::arg128UseAes, yn);
1193   - (*t)["force-V4"] = oe_bare(&ArgParser::arg128ForceV4);
1194   -
1195   - t = &this->encrypt256_option_table;
1196   - (*t)["force-R5"] = oe_bare(&ArgParser::arg256ForceR5);
1197   - (*t)["allow-insecure"] = oe_bare(&ArgParser::argAllowInsecure);
1198   -
1199   - t = &this->under_overlay_option_table;
1200   - (*t)[""] = oe_positional(&ArgParser::argUOpositional);
1201   - (*t)["to"] = oe_requiredParameter(
1202   - &ArgParser::argUOto, "page-range");
1203   - (*t)["from"] = oe_requiredParameter(
1204   - &ArgParser::argUOfrom, "page-range");
1205   - (*t)["repeat"] = oe_requiredParameter(
1206   - &ArgParser::argUOrepeat, "page-range");
1207   - (*t)["password"] = oe_requiredParameter(
1208   - &ArgParser::argUOpassword, "password");
1209   - (*t)["--"] = oe_bare(&ArgParser::argEndUnderOverlay);
1210   -
1211   - t = &this->add_attachment_option_table;
1212   - (*t)[""] = oe_positional(&ArgParser::argAApositional);
1213   - (*t)["key"] = oe_requiredParameter(
1214   - &ArgParser::argAAKey, "attachment-key");
1215   - (*t)["filename"] = oe_requiredParameter(
1216   - &ArgParser::argAAFilename, "filename");
1217   - (*t)["creationdate"] = oe_requiredParameter(
1218   - &ArgParser::argAACreationDate, "creation-date");
1219   - (*t)["moddate"] = oe_requiredParameter(
1220   - &ArgParser::argAAModDate, "modification-date");
1221   - (*t)["mimetype"] = oe_requiredParameter(
1222   - &ArgParser::argAAMimeType, "mime/type");
1223   - (*t)["description"] = oe_requiredParameter(
1224   - &ArgParser::argAADescription, "description");
1225   - (*t)["replace"] = oe_bare(&ArgParser::argAAReplace);
1226   - (*t)["--"] = oe_bare(&ArgParser::argEndAddAttachment);
1227   -
1228   - t = &this->copy_attachments_option_table;
1229   - (*t)[""] = oe_positional(&ArgParser::argCApositional);
1230   - (*t)["prefix"] = oe_requiredParameter(
1231   - &ArgParser::argCAprefix, "prefix");
1232   - (*t)["password"] = oe_requiredParameter(
1233   - &ArgParser::argCApassword, "password");
1234   - (*t)["--"] = oe_bare(&ArgParser::argEndCopyAttachments);
  1037 + this->ap.addRequiredChoices("json-key",
  1038 + p(&ArgParser::argJsonKey), json_key_choices);
  1039 + this->ap.addRequiredParameter("json-object",
  1040 + p(&ArgParser::argJsonObject), "trailer|obj[,gen]");
  1041 + this->ap.addBare("check", b(&ArgParser::argCheck));
  1042 + this->ap.addBare("optimize-images", b(&ArgParser::argOptimizeImages));
  1043 + this->ap.addBare("externalize-inline-images", b(&ArgParser::argExternalizeInlineImages));
  1044 + this->ap.addBare("keep-inline-images", b(&ArgParser::argKeepInlineImages));
  1045 + this->ap.addBare("remove-page-labels", b(&ArgParser::argRemovePageLabels));
  1046 + this->ap.addRequiredParameter("oi-min-width",
  1047 + p(&ArgParser::argOiMinWidth), "minimum-width");
  1048 + this->ap.addRequiredParameter("oi-min-height",
  1049 + p(&ArgParser::argOiMinHeight), "minimum-height");
  1050 + this->ap.addRequiredParameter("oi-min-area",
  1051 + p(&ArgParser::argOiMinArea), "minimum-area");
  1052 + this->ap.addRequiredParameter("ii-min-bytes",
  1053 + p(&ArgParser::argIiMinBytes), "minimum-bytes");
  1054 + this->ap.addBare("overlay", b(&ArgParser::argOverlay));
  1055 + this->ap.addBare("underlay", b(&ArgParser::argUnderlay));
  1056 + this->ap.addBare("replace-input", b(&ArgParser::argReplaceInput));
  1057 + this->ap.addBare("is-encrypted", b(&ArgParser::argIsEncrypted));
  1058 + this->ap.addBare("requires-password", b(&ArgParser::argRequiresPassword));
  1059 + this->ap.addBare("allow-weak-crypto", b(&ArgParser::argAllowWeakCrypto));
  1060 +
  1061 + this->ap.selectMainOptionTable();
  1062 + this->ap.addBare("pages", b(&ArgParser::argPages));
  1063 + this->ap.registerOptionTable(O_PAGES, b(&ArgParser::argEndPages));
  1064 + this->ap.addRequiredParameter(
  1065 + "password", p(&ArgParser::argPagesPassword), "password");
  1066 + this->ap.addPositional(p(&ArgParser::argPagesPositional));
  1067 +
  1068 + this->ap.selectMainOptionTable();
  1069 + this->ap.addBare("encrypt", b(&ArgParser::argEncrypt));
  1070 + this->ap.registerOptionTable(O_ENCRYPT, b(&ArgParser::argEndEncrypt));
  1071 + this->ap.addPositional(p(&ArgParser::argEncryptPositional));
  1072 + this->ap.registerOptionTable(O_ENCRYPT_40, b(&ArgParser::argEndEncrypt));
  1073 + this->ap.addRequiredChoices("extract",p(&ArgParser::arg40Extract), yn);
  1074 + this->ap.addRequiredChoices("annotate",p(&ArgParser::arg40Annotate), yn);
  1075 + this->ap.addRequiredChoices("print",p(&ArgParser::arg40Print), yn);
  1076 + this->ap.addRequiredChoices("modify",p(&ArgParser::arg40Modify), yn);
  1077 + this->ap.registerOptionTable(O_ENCRYPT_128, b(&ArgParser::argEndEncrypt));
  1078 + this->ap.registerOptionTable(O_ENCRYPT_256, b(&ArgParser::argEndEncrypt));
  1079 + for (char const* k: {O_ENCRYPT_128, O_ENCRYPT_256})
  1080 + {
  1081 + this->ap.selectOptionTable(k);
  1082 + this->ap.addRequiredChoices("accessibility",
  1083 + p(&ArgParser::arg128Accessibility), yn);
  1084 + this->ap.addRequiredChoices("extract", p(&ArgParser::arg128Extract), yn);
  1085 + char const* print128_choices[] = {"full", "low", "none", 0};
  1086 + this->ap.addRequiredChoices("print",
  1087 + p(&ArgParser::arg128Print), print128_choices);
  1088 + this->ap.addRequiredChoices("assemble",p(&ArgParser::arg128Assemble), yn);
  1089 + this->ap.addRequiredChoices("annotate",p(&ArgParser::arg128Annotate), yn);
  1090 + this->ap.addRequiredChoices("form",p(&ArgParser::arg128Form), yn);
  1091 + this->ap.addRequiredChoices("modify-other",p(&ArgParser::arg128ModOther), yn);
  1092 + char const* modify128_choices[] =
  1093 + {"all", "annotate", "form", "assembly", "none", 0};
  1094 + this->ap.addRequiredChoices("modify",
  1095 + p(&ArgParser::arg128Modify), modify128_choices);
  1096 + this->ap.addBare("cleartext-metadata", b(&ArgParser::arg128ClearTextMetadata));
  1097 + }
  1098 +
  1099 + this->ap.selectOptionTable(O_ENCRYPT_128);
  1100 + this->ap.addRequiredChoices("use-aes",p(&ArgParser::arg128UseAes), yn);
  1101 + this->ap.addBare("force-V4", b(&ArgParser::arg128ForceV4));
  1102 +
  1103 + this->ap.selectOptionTable(O_ENCRYPT_256);
  1104 + this->ap.addBare("force-R5", b(&ArgParser::arg256ForceR5));
  1105 + this->ap.addBare("allow-insecure", b(&ArgParser::argAllowInsecure));
  1106 +
  1107 + this->ap.registerOptionTable(O_UNDER_OVERLAY, b(&ArgParser::argEndUnderOverlay));
  1108 + this->ap.addPositional(p(&ArgParser::argUOpositional));
  1109 + this->ap.addRequiredParameter("to",
  1110 + p(&ArgParser::argUOto), "page-range");
  1111 + this->ap.addRequiredParameter("from",
  1112 + p(&ArgParser::argUOfrom), "page-range");
  1113 + this->ap.addRequiredParameter("repeat",
  1114 + p(&ArgParser::argUOrepeat), "page-range");
  1115 + this->ap.addRequiredParameter("password",
  1116 + p(&ArgParser::argUOpassword), "password");
  1117 +
  1118 + this->ap.registerOptionTable(O_ATTACHMENT, b(&ArgParser::argEndAddAttachment));
  1119 + this->ap.addPositional(p(&ArgParser::argAApositional));
  1120 + this->ap.addRequiredParameter("key",
  1121 + p(&ArgParser::argAAKey), "attachment-key");
  1122 + this->ap.addRequiredParameter("filename",
  1123 + p(&ArgParser::argAAFilename), "filename");
  1124 + this->ap.addRequiredParameter("creationdate",
  1125 + p(&ArgParser::argAACreationDate), "creation-date");
  1126 + this->ap.addRequiredParameter("moddate",
  1127 + p(&ArgParser::argAAModDate), "modification-date");
  1128 + this->ap.addRequiredParameter("mimetype",
  1129 + p(&ArgParser::argAAMimeType), "mime/type");
  1130 + this->ap.addRequiredParameter("description",
  1131 + p(&ArgParser::argAADescription), "description");
  1132 + this->ap.addBare("replace", b(&ArgParser::argAAReplace));
  1133 +
  1134 + this->ap.registerOptionTable(O_COPY_ATTACHMENT, b(&ArgParser::argEndCopyAttachments));
  1135 + this->ap.addPositional(p(&ArgParser::argCApositional));
  1136 + this->ap.addRequiredParameter("prefix",
  1137 + p(&ArgParser::argCAprefix), "prefix");
  1138 + this->ap.addRequiredParameter("password",
  1139 + p(&ArgParser::argCApassword), "password");
1235 1140 }
1236 1141  
1237 1142 void
... ... @@ -1818,58 +1723,6 @@ ArgParser::argHelp()
1818 1723 }
1819 1724  
1820 1725 void
1821   -ArgParser::completionCommon(bool zsh)
1822   -{
1823   - std::string progname = argv[0];
1824   - std::string executable;
1825   - std::string appdir;
1826   - std::string appimage;
1827   - if (QUtil::get_env("QPDF_EXECUTABLE", &executable))
1828   - {
1829   - progname = executable;
1830   - }
1831   - else if (QUtil::get_env("APPDIR", &appdir) &&
1832   - QUtil::get_env("APPIMAGE", &appimage))
1833   - {
1834   - // Detect if we're in an AppImage and adjust
1835   - if ((appdir.length() < strlen(argv[0])) &&
1836   - (strncmp(appdir.c_str(), argv[0], appdir.length()) == 0))
1837   - {
1838   - progname = appimage;
1839   - }
1840   - }
1841   - if (zsh)
1842   - {
1843   - std::cout << "autoload -U +X bashcompinit && bashcompinit && ";
1844   - }
1845   - std::cout << "complete -o bashdefault -o default";
1846   - if (! zsh)
1847   - {
1848   - std::cout << " -o nospace";
1849   - }
1850   - std::cout << " -C " << progname << " " << whoami << std::endl;
1851   - // Put output before error so calling from zsh works properly
1852   - std::string path = progname;
1853   - size_t slash = path.find('/');
1854   - if ((slash != 0) && (slash != std::string::npos))
1855   - {
1856   - std::cerr << "WARNING: qpdf completion enabled"
1857   - << " using relative path to qpdf" << std::endl;
1858   - }
1859   -}
1860   -
1861   -void
1862   -ArgParser::argCompletionBash()
1863   -{
1864   - completionCommon(false);
1865   -}
1866   -
1867   -void
1868   -ArgParser::argCompletionZsh()
1869   -{
1870   - completionCommon(true);
1871   -}
1872   -void
1873 1726 ArgParser::argJsonHelp()
1874 1727 {
1875 1728 // Make sure the output looks right on an 80-column display.
... ... @@ -1962,50 +1815,54 @@ ArgParser::argLinearize()
1962 1815 void
1963 1816 ArgParser::argEncrypt()
1964 1817 {
1965   - ++cur_arg;
1966   - if (cur_arg + 3 > argc)
  1818 + this->accumulated_args.clear();
  1819 + if (this->ap.isCompleting() && this->ap.argsLeft() == 0)
  1820 + {
  1821 + this->ap.insertCompletion("user-password");
  1822 + }
  1823 + this->ap.selectOptionTable(O_ENCRYPT);
  1824 +}
  1825 +
  1826 +void
  1827 +ArgParser::argEncryptPositional(char* arg)
  1828 +{
  1829 + this->accumulated_args.push_back(arg);
  1830 + size_t n_args = this->accumulated_args.size();
  1831 + if (n_args < 3)
1967 1832 {
1968   - if (this->bash_completion)
  1833 + if (this->ap.isCompleting() && (this->ap.argsLeft() == 0))
1969 1834 {
1970   - if (cur_arg == argc)
1971   - {
1972   - this->completions.insert("user-password");
1973   - }
1974   - else if (cur_arg + 1 == argc)
  1835 + if (n_args == 1)
1975 1836 {
1976   - this->completions.insert("owner-password");
  1837 + this->ap.insertCompletion("owner-password");
1977 1838 }
1978   - else if (cur_arg + 2 == argc)
  1839 + else if (n_args == 2)
1979 1840 {
1980   - this->completions.insert("40");
1981   - this->completions.insert("128");
1982   - this->completions.insert("256");
  1841 + this->ap.insertCompletion("40");
  1842 + this->ap.insertCompletion("128");
  1843 + this->ap.insertCompletion("256");
1983 1844 }
1984   - return;
1985   - }
1986   - else
1987   - {
1988   - usage("insufficient arguments to --encrypt");
1989 1845 }
  1846 + return;
1990 1847 }
1991   - o.user_password = argv[cur_arg++];
1992   - o.owner_password = argv[cur_arg++];
1993   - std::string len_str = argv[cur_arg];
  1848 + o.user_password = this->accumulated_args.at(0);
  1849 + o.owner_password = this->accumulated_args.at(1);
  1850 + std::string len_str = this->accumulated_args.at(2);
1994 1851 if (len_str == "40")
1995 1852 {
1996 1853 o.keylen = 40;
1997   - this->option_table = &(this->encrypt40_option_table);
  1854 + this->ap.selectOptionTable(O_ENCRYPT_40);
1998 1855 }
1999 1856 else if (len_str == "128")
2000 1857 {
2001 1858 o.keylen = 128;
2002   - this->option_table = &(this->encrypt128_option_table);
  1859 + this->ap.selectOptionTable(O_ENCRYPT_128);
2003 1860 }
2004 1861 else if (len_str == "256")
2005 1862 {
2006 1863 o.keylen = 256;
2007 1864 o.use_aes = true;
2008   - this->option_table = &(this->encrypt256_option_table);
  1865 + this->ap.selectOptionTable(O_ENCRYPT_256);
2009 1866 }
2010 1867 else
2011 1868 {
... ... @@ -2096,12 +1953,116 @@ ArgParser::argCollate(char* parameter)
2096 1953 void
2097 1954 ArgParser::argPages()
2098 1955 {
2099   - ++cur_arg;
2100 1956 if (! o.page_specs.empty())
2101 1957 {
2102 1958 usage("the --pages may only be specified one time");
2103 1959 }
2104   - o.page_specs = parsePagesOptions();
  1960 + this->accumulated_args.clear();
  1961 + this->ap.selectOptionTable(O_PAGES);
  1962 +}
  1963 +
  1964 +void
  1965 +ArgParser::argPagesPassword(char* parameter)
  1966 +{
  1967 + if (this->pages_password != nullptr)
  1968 + {
  1969 + QTC::TC("qpdf", "qpdf duplicated pages password");
  1970 + usage("--password already specified for this file");
  1971 + }
  1972 + if (this->accumulated_args.size() != 1)
  1973 + {
  1974 + QTC::TC("qpdf", "qpdf misplaced pages password");
  1975 + usage("in --pages, --password must immediately follow a file name");
  1976 + }
  1977 + this->pages_password = parameter;
  1978 +}
  1979 +
  1980 +void
  1981 +ArgParser::argPagesPositional(char* arg)
  1982 +{
  1983 + if (arg == nullptr)
  1984 + {
  1985 + if (this->accumulated_args.empty())
  1986 + {
  1987 + return;
  1988 + }
  1989 + }
  1990 + else
  1991 + {
  1992 + this->accumulated_args.push_back(arg);
  1993 + }
  1994 +
  1995 + char const* file = this->accumulated_args.at(0);
  1996 + char const* range = nullptr;
  1997 +
  1998 + size_t n_args = this->accumulated_args.size();
  1999 + if (n_args >= 2)
  2000 + {
  2001 + range = this->accumulated_args.at(1);
  2002 + }
  2003 +
  2004 + // See if the user omitted the range entirely, in which case we
  2005 + // assume "1-z".
  2006 + char* next_file = nullptr;
  2007 + if (range == nullptr)
  2008 + {
  2009 + if (arg == nullptr)
  2010 + {
  2011 + // The filename or password was the last argument
  2012 + QTC::TC("qpdf", "qpdf pages range omitted at end",
  2013 + this->pages_password == nullptr ? 0 : 1);
  2014 + }
  2015 + else
  2016 + {
  2017 + // We need to accumulate some more arguments
  2018 + return;
  2019 + }
  2020 + }
  2021 + else
  2022 + {
  2023 + try
  2024 + {
  2025 + parseNumrange(range, 0, true);
  2026 + }
  2027 + catch (std::runtime_error& e1)
  2028 + {
  2029 + // The range is invalid. Let's see if it's a file.
  2030 + if (strcmp(range, ".") == 0)
  2031 + {
  2032 + // "." means the input file.
  2033 + QTC::TC("qpdf", "qpdf pages range omitted with .");
  2034 + }
  2035 + else if (QUtil::file_can_be_opened(range))
  2036 + {
  2037 + QTC::TC("qpdf", "qpdf pages range omitted in middle");
  2038 + // Yup, it's a file.
  2039 + }
  2040 + else
  2041 + {
  2042 + // Give the range error
  2043 + usage(e1.what());
  2044 + }
  2045 + next_file = const_cast<char*>(range);
  2046 + range = nullptr;
  2047 + }
  2048 + }
  2049 + if (range == nullptr)
  2050 + {
  2051 + range = "1-z";
  2052 + }
  2053 + o.page_specs.push_back(PageSpec(file, this->pages_password, range));
  2054 + this->accumulated_args.clear();
  2055 + this->pages_password = nullptr;
  2056 + if (next_file != nullptr)
  2057 + {
  2058 + this->accumulated_args.push_back(next_file);
  2059 + }
  2060 +}
  2061 +
  2062 +void
  2063 +ArgParser::argEndPages()
  2064 +{
  2065 + argPagesPositional(nullptr);
2105 2066 if (o.page_specs.empty())
2106 2067 {
2107 2068 usage("--pages: no page specifications given");
... ... @@ -2155,15 +2116,15 @@ ArgParser::argRemoveAttachment(char* parameter)
2155 2116 void
2156 2117 ArgParser::argAddAttachment()
2157 2118 {
2158   - this->option_table = &(this->add_attachment_option_table);
2159 2119 o.attachments_to_add.push_back(AddAttachment());
  2120 + this->ap.selectOptionTable(O_ATTACHMENT);
2160 2121 }
2161 2122  
2162 2123 void
2163 2124 ArgParser::argCopyAttachments()
2164 2125 {
2165   - this->option_table = &(this->copy_attachments_option_table);
2166 2126 o.attachments_to_copy.push_back(CopyAttachmentFrom());
  2127 + this->ap.selectOptionTable(O_COPY_ATTACHMENT);
2167 2128 }
2168 2129  
2169 2130 void
... ... @@ -2743,7 +2704,6 @@ ArgParser::argEndEncrypt()
2743 2704 o.encrypt = true;
2744 2705 o.decrypt = false;
2745 2706 o.copy_encryption = false;
2746   - this->option_table = &(this->main_option_table);
2747 2707 }
2748 2708  
2749 2709 void
... ... @@ -2795,7 +2755,6 @@ ArgParser::argUOpassword(char* parameter)
2795 2755 void
2796 2756 ArgParser::argEndUnderOverlay()
2797 2757 {
2798   - this->option_table = &(this->main_option_table);
2799 2758 if (0 == o.under_overlay->filename)
2800 2759 {
2801 2760 usage(o.under_overlay->which + " file not specified");
... ... @@ -2888,7 +2847,6 @@ ArgParser::argEndAddAttachment()
2888 2847 {
2889 2848 static std::string now = QUtil::qpdf_time_to_pdf_time(
2890 2849 QUtil::get_current_qpdf_time());
2891   - this->option_table = &(this->main_option_table);
2892 2850 auto& cur = o.attachments_to_add.back();
2893 2851 if (cur.path.empty())
2894 2852 {
... ... @@ -2938,161 +2896,12 @@ ArgParser::argCApassword(char* parameter)
2938 2896 void
2939 2897 ArgParser::argEndCopyAttachments()
2940 2898 {
2941   - this->option_table = &(this->main_option_table);
2942 2899 if (o.attachments_to_copy.back().path.empty())
2943 2900 {
2944 2901 usage("copy attachments: no path specified");
2945 2902 }
2946 2903 }
2947 2904  
2948   -void
2949   -ArgParser::handleArgFileArguments()
2950   -{
2951   - // Support reading arguments from files. Create a new argv. Ensure
2952   - // that argv itself as well as all its contents are automatically
2953   - // deleted by using PointerHolder objects to back the pointers in
2954   - // argv.
2955   - new_argv.push_back(PointerHolder<char>(true, QUtil::copy_string(argv[0])));
2956   - for (int i = 1; i < argc; ++i)
2957   - {
2958   - char* argfile = 0;
2959   - if ((strlen(argv[i]) > 1) && (argv[i][0] == '@'))
2960   - {
2961   - argfile = 1 + argv[i];
2962   - if (strcmp(argfile, "-") != 0)
2963   - {
2964   - if (! QUtil::file_can_be_opened(argfile))
2965   - {
2966   - // The file's not there; treating as regular option
2967   - argfile = nullptr;
2968   - }
2969   - }
2970   - }
2971   - if (argfile)
2972   - {
2973   - readArgsFromFile(1+argv[i]);
2974   - }
2975   - else
2976   - {
2977   - new_argv.push_back(
2978   - PointerHolder<char>(true, QUtil::copy_string(argv[i])));
2979   - }
2980   - }
2981   - argv_ph = PointerHolder<char*>(true, new char*[1+new_argv.size()]);
2982   - argv = argv_ph.getPointer();
2983   - for (size_t i = 0; i < new_argv.size(); ++i)
2984   - {
2985   - argv[i] = new_argv.at(i).getPointer();
2986   - }
2987   - argc = QIntC::to_int(new_argv.size());
2988   - argv[argc] = 0;
2989   -}
2990   -
2991   -void
2992   -ArgParser::handleBashArguments()
2993   -{
2994   - // Do a minimal job of parsing bash_line into arguments. This
2995   - // doesn't do everything the shell does (e.g. $(...), variable
2996   - // expansion, arithmetic, globs, etc.), but it should be good
2997   - // enough for purposes of handling completion. As we build up the
2998   - // new argv, we can't use this->new_argv because this code has to
2999   - // interoperate with @file arguments, so memory for both ways of
3000   - // fabricating argv has to be protected.
3001   -
3002   - bool last_was_backslash = false;
3003   - enum { st_top, st_squote, st_dquote } state = st_top;
3004   - std::string arg;
3005   - for (std::string::iterator iter = bash_line.begin();
3006   - iter != bash_line.end(); ++iter)
3007   - {
3008   - char ch = (*iter);
3009   - if (last_was_backslash)
3010   - {
3011   - arg.append(1, ch);
3012   - last_was_backslash = false;
3013   - }
3014   - else if (ch == '\\')
3015   - {
3016   - last_was_backslash = true;
3017   - }
3018   - else
3019   - {
3020   - bool append = false;
3021   - switch (state)
3022   - {
3023   - case st_top:
3024   - if (QUtil::is_space(ch))
3025   - {
3026   - if (! arg.empty())
3027   - {
3028   - bash_argv.push_back(
3029   - PointerHolder<char>(
3030   - true, QUtil::copy_string(arg.c_str())));
3031   - arg.clear();
3032   - }
3033   - }
3034   - else if (ch == '"')
3035   - {
3036   - state = st_dquote;
3037   - }
3038   - else if (ch == '\'')
3039   - {
3040   - state = st_squote;
3041   - }
3042   - else
3043   - {
3044   - append = true;
3045   - }
3046   - break;
3047   -
3048   - case st_squote:
3049   - if (ch == '\'')
3050   - {
3051   - state = st_top;
3052   - }
3053   - else
3054   - {
3055   - append = true;
3056   - }
3057   - break;
3058   -
3059   - case st_dquote:
3060   - if (ch == '"')
3061   - {
3062   - state = st_top;
3063   - }
3064   - else
3065   - {
3066   - append = true;
3067   - }
3068   - break;
3069   - }
3070   - if (append)
3071   - {
3072   - arg.append(1, ch);
3073   - }
3074   - }
3075   - }
3076   - if (bash_argv.empty())
3077   - {
3078   - // This can't happen if properly invoked by bash, but ensure
3079   - // we have a valid argv[0] regardless.
3080   - bash_argv.push_back(
3081   - PointerHolder<char>(
3082   - true, QUtil::copy_string(argv[0])));
3083   - }
3084   - // Explicitly discard any non-space-terminated word. The "current
3085   - // word" is handled specially.
3086   - bash_argv_ph = PointerHolder<char*>(true, new char*[1+bash_argv.size()]);
3087   - argv = bash_argv_ph.getPointer();
3088   - for (size_t i = 0; i < bash_argv.size(); ++i)
3089   - {
3090   - argv[i] = bash_argv.at(i).getPointer();
3091   - }
3092   - argc = QIntC::to_int(bash_argv.size());
3093   - argv[argc] = 0;
3094   -}
3095   -
3096 2905 void usageExit(std::string const& msg)
3097 2906 {
3098 2907 std::cerr
... ... @@ -3108,7 +2917,7 @@ void usageExit(std::string const&amp; msg)
3108 2917 void
3109 2918 ArgParser::usage(std::string const& message)
3110 2919 {
3111   - if (this->bash_completion)
  2920 + if (this->ap.isCompleting())
3112 2921 {
3113 2922 // This will cause bash to fall back to regular file completion.
3114 2923 exit(0);
... ... @@ -3234,101 +3043,11 @@ ArgParser::parseNumrange(char const* range, int max, bool throw_error)
3234 3043 return std::vector<int>();
3235 3044 }
3236 3045  
3237   -std::vector<PageSpec>
3238   -ArgParser::parsePagesOptions()
3239   -{
3240   - auto check_unclosed = [this](char const* arg, int n) {
3241   - if ((strlen(arg) > 0) && (arg[0] == '-'))
3242   - {
3243   - // A common error is to forget to close --pages with --,
3244   - // so catch this as special case
3245   - QTC::TC("qpdf", "check unclosed --pages", n);
3246   - usage("the --pages option must be terminated with -- by itself");
3247   - }
3248   - };
3249   -
3250   - std::vector<PageSpec> result;
3251   - while (1)
3252   - {
3253   - if ((cur_arg < argc) && (strcmp(argv[cur_arg], "--") == 0))
3254   - {
3255   - break;
3256   - }
3257   - if (cur_arg + 1 >= argc)
3258   - {
3259   - usage("insufficient arguments to --pages");
3260   - }
3261   - char const* file = argv[cur_arg++];
3262   - char const* password = 0;
3263   - char const* range = argv[cur_arg++];
3264   - if (! QUtil::file_can_be_opened(file))
3265   - {
3266   - check_unclosed(file, 0);
3267   - }
3268   - if (strncmp(range, "--password=", 11) == 0)
3269   - {
3270   - // Oh, that's the password, not the range
3271   - if (cur_arg + 1 >= argc)
3272   - {
3273   - usage("insufficient arguments to --pages");
3274   - }
3275   - password = range + 11;
3276   - range = argv[cur_arg++];
3277   - }
3278   -
3279   - // See if the user omitted the range entirely, in which case
3280   - // we assume "1-z".
3281   - bool range_omitted = false;
3282   - if (strcmp(range, "--") == 0)
3283   - {
3284   - // The filename or password was the last argument
3285   - QTC::TC("qpdf", "qpdf pages range omitted at end");
3286   - range_omitted = true;
3287   - }
3288   - else
3289   - {
3290   - try
3291   - {
3292   - parseNumrange(range, 0, true);
3293   - }
3294   - catch (std::runtime_error& e1)
3295   - {
3296   - // The range is invalid. Let's see if it's a file.
3297   - range_omitted = true;
3298   - if (strcmp(range, ".") == 0)
3299   - {
3300   - // "." means the input file.
3301   - QTC::TC("qpdf", "qpdf pages range omitted with .");
3302   - }
3303   - else if (QUtil::file_can_be_opened(range))
3304   - {
3305   - QTC::TC("qpdf", "qpdf pages range omitted in middle");
3306   - // Yup, it's a file.
3307   - }
3308   - else
3309   - {
3310   - check_unclosed(range, 1);
3311   - // Give the range error
3312   - usage(e1.what());
3313   - }
3314   - }
3315   - }
3316   - if (range_omitted)
3317   - {
3318   - --cur_arg;
3319   - range = "1-z";
3320   - }
3321   -
3322   - result.push_back(PageSpec(file, password, range));
3323   - }
3324   - return result;
3325   -}
3326   -
3327 3046 void
3328 3047 ArgParser::parseUnderOverlayOptions(UnderOverlay* uo)
3329 3048 {
3330 3049 o.under_overlay = uo;
3331   - this->option_table = &(this->under_overlay_option_table);
  3050 + this->ap.selectOptionTable(O_UNDER_OVERLAY);
3332 3051 }
3333 3052  
3334 3053 QPDFPageData::QPDFPageData(std::string const& filename,
... ... @@ -3374,28 +3093,6 @@ static void parse_version(std::string const&amp; full_version_string,
3374 3093 }
3375 3094  
3376 3095 void
3377   -ArgParser::readArgsFromFile(char const* filename)
3378   -{
3379   - std::list<std::string> lines;
3380   - if (strcmp(filename, "-") == 0)
3381   - {
3382   - QTC::TC("qpdf", "qpdf read args from stdin");
3383   - lines = QUtil::read_lines_from_file(std::cin);
3384   - }
3385   - else
3386   - {
3387   - QTC::TC("qpdf", "qpdf read args from file");
3388   - lines = QUtil::read_lines_from_file(filename);
3389   - }
3390   - for (std::list<std::string>::iterator iter = lines.begin();
3391   - iter != lines.end(); ++iter)
3392   - {
3393   - new_argv.push_back(
3394   - PointerHolder<char>(true, QUtil::copy_string((*iter).c_str())));
3395   - }
3396   -}
3397   -
3398   -void
3399 3096 ArgParser::parseRotationParameter(std::string const& parameter)
3400 3097 {
3401 3098 std::string angle_str;
... ... @@ -3462,232 +3159,21 @@ ArgParser::parseRotationParameter(std::string const&amp; parameter)
3462 3159 }
3463 3160  
3464 3161 void
3465   -ArgParser::checkCompletion()
3466   -{
3467   - // See if we're being invoked from bash completion.
3468   - std::string bash_point_env;
3469   - // On Windows with mingw, there have been times when there appears
3470   - // to be no way to distinguish between an empty environment
3471   - // variable and an unset variable. There are also conditions under
3472   - // which bash doesn't set COMP_LINE. Therefore, enter this logic
3473   - // if either COMP_LINE or COMP_POINT are set. They will both be
3474   - // set together under ordinary circumstances.
3475   - bool got_line = QUtil::get_env("COMP_LINE", &bash_line);
3476   - bool got_point = QUtil::get_env("COMP_POINT", &bash_point_env);
3477   - if (got_line || got_point)
3478   - {
3479   - size_t p = QUtil::string_to_uint(bash_point_env.c_str());
3480   - if (p < bash_line.length())
3481   - {
3482   - // Truncate the line. We ignore everything at or after the
3483   - // cursor for completion purposes.
3484   - bash_line = bash_line.substr(0, p);
3485   - }
3486   - if (p > bash_line.length())
3487   - {
3488   - p = bash_line.length();
3489   - }
3490   - // Set bash_cur and bash_prev based on bash_line rather than
3491   - // relying on argv. This enables us to use bashcompinit to get
3492   - // completion in zsh too since bashcompinit sets COMP_LINE and
3493   - // COMP_POINT but doesn't invoke the command with options like
3494   - // bash does.
3495   -
3496   - // p is equal to length of the string. Walk backwards looking
3497   - // for the first separator. bash_cur is everything after the
3498   - // last separator, possibly empty.
3499   - char sep(0);
3500   - while (p > 0)
3501   - {
3502   - --p;
3503   - char ch = bash_line.at(p);
3504   - if ((ch == ' ') || (ch == '=') || (ch == ':'))
3505   - {
3506   - sep = ch;
3507   - break;
3508   - }
3509   - }
3510   - if (1+p <= bash_line.length())
3511   - {
3512   - bash_cur = bash_line.substr(1+p, std::string::npos);
3513   - }
3514   - if ((sep == ':') || (sep == '='))
3515   - {
3516   - // Bash sets prev to the non-space separator if any.
3517   - // Actually, if there are multiple separators in a row,
3518   - // they are all included in prev, but that detail is not
3519   - // important to us and not worth coding.
3520   - bash_prev = bash_line.substr(p, 1);
3521   - }
3522   - else
3523   - {
3524   - // Go back to the last separator and set prev based on
3525   - // that.
3526   - size_t p1 = p;
3527   - while (p1 > 0)
3528   - {
3529   - --p1;
3530   - char ch = bash_line.at(p1);
3531   - if ((ch == ' ') || (ch == ':') || (ch == '='))
3532   - {
3533   - bash_prev = bash_line.substr(p1 + 1, p - p1 - 1);
3534   - break;
3535   - }
3536   - }
3537   - }
3538   - if (bash_prev.empty())
3539   - {
3540   - bash_prev = bash_line.substr(0, p);
3541   - }
3542   - if (argc == 1)
3543   - {
3544   - // This is probably zsh using bashcompinit. There are a
3545   - // few differences in the expected output.
3546   - zsh_completion = true;
3547   - }
3548   - handleBashArguments();
3549   - bash_completion = true;
3550   - }
3551   -}
3552   -
3553   -void
3554 3162 ArgParser::parseOptions()
3555 3163 {
3556   - checkCompletion();
3557   - handleArgFileArguments();
3558   - for (cur_arg = 1; cur_arg < argc; ++cur_arg)
3559   - {
3560   - bool help_option = false;
3561   - auto oep = this->option_table->end();
3562   - char* arg = argv[cur_arg];
3563   - char* parameter = nullptr;
3564   - std::string o_arg(arg);
3565   - std::string arg_s(arg);
3566   - if (strcmp(arg, "--") == 0)
3567   - {
3568   - // Special case for -- option, which is used to break out
3569   - // of subparsers.
3570   - oep = this->option_table->find("--");
3571   - if (oep == this->option_table->end())
3572   - {
3573   - throw std::logic_error("ArgParser: -- handler not registered");
3574   - }
3575   - }
3576   - else if ((arg[0] == '-') && (strcmp(arg, "-") != 0))
3577   - {
3578   - ++arg;
3579   - if (arg[0] == '-')
3580   - {
3581   - // Be lax about -arg vs --arg
3582   - ++arg;
3583   - }
3584   - if (strlen(arg) > 0)
3585   - {
3586   - // Prevent --=something from being treated as an empty
3587   - // arg since the empty string in the option table is
3588   - // for positional arguments.
3589   - parameter = const_cast<char*>(strchr(1 + arg, '='));
3590   - }
3591   - if (parameter)
3592   - {
3593   - *parameter++ = 0;
3594   - }
3595   -
3596   - arg_s = arg;
3597   - if (! (arg_s.empty() || (arg_s.at(0) == '-')))
3598   - {
3599   - oep = this->option_table->find(arg_s);
3600   - }
3601   -
3602   - if ((! this->bash_completion) &&
3603   - (argc == 2) && (cur_arg == 1) &&
3604   - (oep == this->option_table->end()))
3605   - {
3606   - // Handle help option, which is only valid as the sole
3607   - // option.
3608   - oep = this->help_option_table.find(arg_s);
3609   - help_option = true;
3610   - }
3611   - }
3612   - else
3613   - {
3614   - // The empty string maps to the positional argument
3615   - // handler.
3616   - oep = this->option_table->find("");
3617   - parameter = arg;
3618   - }
3619   -
3620   - if (oep == this->option_table->end())
3621   - {
3622   - usage("unrecognized argument " + o_arg);
3623   - }
3624   -
3625   - OptionEntry& oe = oep->second;
3626   - if ((oe.parameter_needed && (0 == parameter)) ||
3627   - ((! oe.choices.empty() &&
3628   - ((0 == parameter) ||
3629   - (0 == oe.choices.count(parameter))))))
3630   - {
3631   - std::string message =
3632   - "--" + arg_s + " must be given as --" + arg_s + "=";
3633   - if (! oe.choices.empty())
3634   - {
3635   - QTC::TC("qpdf", "qpdf required choices");
3636   - message += "{";
3637   - for (std::set<std::string>::iterator iter =
3638   - oe.choices.begin();
3639   - iter != oe.choices.end(); ++iter)
3640   - {
3641   - if (iter != oe.choices.begin())
3642   - {
3643   - message += ",";
3644   - }
3645   - message += *iter;
3646   - }
3647   - message += "}";
3648   - }
3649   - else if (! oe.parameter_name.empty())
3650   - {
3651   - QTC::TC("qpdf", "qpdf required parameter");
3652   - message += oe.parameter_name;
3653   - }
3654   - else
3655   - {
3656   - // should not be possible
3657   - message += "option";
3658   - }
3659   - usage(message);
3660   - }
3661   - if (oe.bare_arg_handler)
3662   - {
3663   - (this->*(oe.bare_arg_handler))();
3664   - }
3665   - else if (oe.param_arg_handler)
3666   - {
3667   - (this->*(oe.param_arg_handler))(parameter);
3668   - }
3669   - if (help_option)
3670   - {
3671   - exit(0);
3672   - }
3673   - }
3674   - if (this->bash_completion)
  3164 + try
3675 3165 {
3676   - handleCompletion();
  3166 + this->ap.parseArgs();
3677 3167 }
3678   - else
  3168 + catch (QPDFArgParser::Usage& e)
3679 3169 {
3680   - doFinalChecks();
  3170 + usage(e.what());
3681 3171 }
3682 3172 }
3683 3173  
3684 3174 void
3685 3175 ArgParser::doFinalChecks()
3686 3176 {
3687   - if (this->option_table != &(this->main_option_table))
3688   - {
3689   - usage("missing -- at end of options");
3690   - }
3691 3177 if (o.replace_input)
3692 3178 {
3693 3179 if (o.outfilename)
... ... @@ -3769,127 +3255,6 @@ ArgParser::doFinalChecks()
3769 3255 }
3770 3256 }
3771 3257  
3772   -void
3773   -ArgParser::addChoicesToCompletions(std::string const& option,
3774   - std::string const& extra_prefix)
3775   -{
3776   - if (this->option_table->count(option) != 0)
3777   - {
3778   - OptionEntry& oe = (*this->option_table)[option];
3779   - for (std::set<std::string>::iterator iter = oe.choices.begin();
3780   - iter != oe.choices.end(); ++iter)
3781   - {
3782   - completions.insert(extra_prefix + *iter);
3783   - }
3784   - }
3785   -}
3786   -
3787   -void
3788   -ArgParser::addOptionsToCompletions()
3789   -{
3790   - for (std::map<std::string, OptionEntry>::iterator iter =
3791   - this->option_table->begin();
3792   - iter != this->option_table->end(); ++iter)
3793   - {
3794   - std::string const& arg = (*iter).first;
3795   - if (arg == "--")
3796   - {
3797   - continue;
3798   - }
3799   - OptionEntry& oe = (*iter).second;
3800   - std::string base = "--" + arg;
3801   - if (oe.param_arg_handler)
3802   - {
3803   - if (zsh_completion)
3804   - {
3805   - // zsh doesn't treat = as a word separator, so add all
3806   - // the options so we don't get a space after the =.
3807   - addChoicesToCompletions(arg, base + "=");
3808   - }
3809   - completions.insert(base + "=");
3810   - }
3811   - if (! oe.parameter_needed)
3812   - {
3813   - completions.insert(base);
3814   - }
3815   - }
3816   -}
3817   -
3818   -void
3819   -ArgParser::handleCompletion()
3820   -{
3821   - std::string extra_prefix;
3822   - if (this->completions.empty())
3823   - {
3824   - // Detect --option=... Bash treats the = as a word separator.
3825   - std::string choice_option;
3826   - if (bash_cur.empty() && (bash_prev.length() > 2) &&
3827   - (bash_prev.at(0) == '-') &&
3828   - (bash_prev.at(1) == '-') &&
3829   - (bash_line.at(bash_line.length() - 1) == '='))
3830   - {
3831   - choice_option = bash_prev.substr(2, std::string::npos);
3832   - }
3833   - else if ((bash_prev == "=") &&
3834   - (bash_line.length() > (bash_cur.length() + 1)))
3835   - {
3836   - // We're sitting at --option=x. Find previous option.
3837   - size_t end_mark = bash_line.length() - bash_cur.length() - 1;
3838   - char before_cur = bash_line.at(end_mark);
3839   - if (before_cur == '=')
3840   - {
3841   - size_t space = bash_line.find_last_of(' ', end_mark);
3842   - if (space != std::string::npos)
3843   - {
3844   - std::string candidate =
3845   - bash_line.substr(space + 1, end_mark - space - 1);
3846   - if ((candidate.length() > 2) &&
3847   - (candidate.at(0) == '-') &&
3848   - (candidate.at(1) == '-'))
3849   - {
3850   - choice_option =
3851   - candidate.substr(2, std::string::npos);
3852   - }
3853   - }
3854   - }
3855   - }
3856   - if (! choice_option.empty())
3857   - {
3858   - if (zsh_completion)
3859   - {
3860   - // zsh wants --option=choice rather than just choice
3861   - extra_prefix = "--" + choice_option + "=";
3862   - }
3863   - addChoicesToCompletions(choice_option, extra_prefix);
3864   - }
3865   - else if ((! bash_cur.empty()) && (bash_cur.at(0) == '-'))
3866   - {
3867   - addOptionsToCompletions();
3868   - if (this->argc == 1)
3869   - {
3870   - // Help options are valid only by themselves.
3871   - for (std::map<std::string, OptionEntry>::iterator iter =
3872   - this->help_option_table.begin();
3873   - iter != this->help_option_table.end(); ++iter)
3874   - {
3875   - this->completions.insert("--" + (*iter).first);
3876   - }
3877   - }
3878   - }
3879   - }
3880   - std::string prefix = extra_prefix + bash_cur;
3881   - for (std::set<std::string>::iterator iter = completions.begin();
3882   - iter != completions.end(); ++iter)
3883   - {
3884   - if (prefix.empty() ||
3885   - ((*iter).substr(0, prefix.length()) == prefix))
3886   - {
3887   - std::cout << *iter << std::endl;
3888   - }
3889   - }
3890   - exit(0);
3891   -}
3892   -
3893 3258 static void set_qpdf_options(QPDF& pdf, Options& o)
3894 3259 {
3895 3260 if (o.ignore_xref_streams)
... ...
qpdf/qpdf.testcov
... ... @@ -255,7 +255,7 @@ QPDF not caching overridden objstm object 0
255 255 QPDFWriter original obj non-zero gen 0
256 256 QPDF_optimization indirect outlines 0
257 257 QPDF xref space 2
258   -qpdf pages range omitted at end 0
  258 +qpdf pages range omitted at end 1
259 259 qpdf pages range omitted in middle 0
260 260 qpdf npages 0
261 261 QPDF already reserved object 0
... ... @@ -273,8 +273,6 @@ QPDF resolve failure to null 0
273 273 QPDFWriter preserve unreferenced standard 0
274 274 QPDFObjectHandle errors in parsecontent 0
275 275 qpdf same file error 0
276   -qpdf read args from stdin 0
277   -qpdf read args from file 0
278 276 qpdf split-pages %d 0
279 277 qpdf split-pages .pdf 0
280 278 qpdf split-pages other 0
... ... @@ -362,8 +360,6 @@ QPDFOutlineObjectHelper named dest 0
362 360 QPDFOutlineDocumentHelper name named dest 0
363 361 QPDFOutlineDocumentHelper string named dest 0
364 362 QPDFOutlineObjectHelper loop 0
365   -qpdf required parameter 0
366   -qpdf required choices 0
367 363 QPDFObjectHandle merge top type mismatch 0
368 364 QPDFObjectHandle merge shallow copy 0
369 365 QPDFObjectHandle merge array 0
... ... @@ -599,7 +595,6 @@ qpdf copy fields non-first from orig 0
599 595 QPDF resolve duplicated page in insert 0
600 596 QPDFWriter preserve object streams 1
601 597 QPDFWriter exclude from object stream 0
602   -check unclosed --pages 1
603 598 QPDF_pages findPage not found 0
604 599 qpdf overlay page with no resources 0
605 600 QPDFObjectHandle check ownership 0
... ... @@ -629,3 +624,5 @@ qpdf-c called qpdf_oh_replace_stream_data 0
629 624 qpdf-c silence oh errors 0
630 625 qpdf-c called qpdf_oh_get_binary_string_value 0
631 626 qpdf-c called qpdf_oh_new_binary_string 0
  627 +qpdf duplicated pages password 0
  628 +qpdf misplaced pages password 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -149,7 +149,7 @@ foreach my $c (@completion_tests)
149 149 show_ntests();
150 150 # ----------
151 151 $td->notify("--- Argument Parsing ---");
152   -$n_tests += 9;
  152 +$n_tests += 12;
153 153  
154 154 $td->runtest("required argument",
155 155 {$td->COMMAND => "qpdf --password minimal.pdf"},
... ... @@ -182,18 +182,33 @@ $td-&gt;runtest(&quot;extra overlay filename&quot;,
182 182 $td->EXIT_STATUS => 2},
183 183 $td->NORMALIZE_NEWLINES);
184 184 $td->runtest("multiple pages options",
185   - {$td->COMMAND => "qpdf --pages . -- --pages . --"},
  185 + {$td->COMMAND => "qpdf --pages . --password=x -- --pages . --"},
186 186 {$td->REGEXP => ".*--pages may only be specified one time.*",
187 187 $td->EXIT_STATUS => 2},
188 188 $td->NORMALIZE_NEWLINES);
189 189 $td->runtest("bad numeric range detects unclosed --pages",
190 190 {$td->COMMAND => "qpdf --pages . --pages . --"},
191   - {$td->REGEXP => ".*--pages option must be terminated with --.*",
  191 + {$td->REGEXP => ".*pages options must be terminated with --.*",
192 192 $td->EXIT_STATUS => 2},
193 193 $td->NORMALIZE_NEWLINES);
194 194 $td->runtest("bad file detected as unclosed --pages",
195 195 {$td->COMMAND => "qpdf --pages . 1 --xyz out"},
196   - {$td->REGEXP => ".*--pages option must be terminated with --.*",
  196 + {$td->REGEXP => ".*pages options must be terminated with --.*",
  197 + $td->EXIT_STATUS => 2},
  198 + $td->NORMALIZE_NEWLINES);
  199 +$td->runtest("misplaced pages password 1",
  200 + {$td->COMMAND => "qpdf --pages . 1 --password=z --"},
  201 + {$td->REGEXP => ".*password must immediately follow a file name.*",
  202 + $td->EXIT_STATUS => 2},
  203 + $td->NORMALIZE_NEWLINES);
  204 +$td->runtest("misplaced pages password 2",
  205 + {$td->COMMAND => "qpdf --pages --password=z . 1 --"},
  206 + {$td->REGEXP => ".*password must immediately follow a file name.*",
  207 + $td->EXIT_STATUS => 2},
  208 + $td->NORMALIZE_NEWLINES);
  209 +$td->runtest("duplicated pages password",
  210 + {$td->COMMAND => "qpdf --pages . --password=z --password=z --"},
  211 + {$td->REGEXP => ".*password already specified.*",
197 212 $td->EXIT_STATUS => 2},
198 213 $td->NORMALIZE_NEWLINES);
199 214  
... ...