Commit 151206603b80225ff9f59235bf38a3677ebbe6d2

Authored by Jay Berkenbilt
1 parent 6580ffe9

Move argument parsing into a class

Showing 1 changed file with 157 additions and 103 deletions
qpdf/qpdf.cc
... ... @@ -241,6 +241,78 @@ ProgressReporter::reportProgress(int percentage)
241 241 << percentage << "%" << std::endl;
242 242 }
243 243  
  244 +// This is not a general-purpose argument parser. It is tightly
  245 +// crafted to work with qpdf. qpdf's command-line syntax is very
  246 +// complex because of its long history, and it doesn't really follow
  247 +// any kind of normal standard for arguments, but I don't want to
  248 +// break compatibility by changing what constitutes a valid command.
  249 +// This class is intended to simplify the argument parsing code and
  250 +// also to make it possible to add bash completion support while
  251 +// guaranteeing consistency with the actual argument syntax.
  252 +class ArgParser
  253 +{
  254 + public:
  255 + ArgParser(int argc, char* argv[], Options& o);
  256 + void parseOptions();
  257 +
  258 + private:
  259 + void usage(std::string const& message);
  260 + void handleHelpVersion();
  261 + void handleArgFileArguments();
  262 + void readArgsFromFile(char const* filename);
  263 + void parseEncryptOptions(int& cur_arg);
  264 + std::vector<PageSpec> parsePagesOptions(int& cur_arg);
  265 + void parseRotationParameter(std::string const&);
  266 + std::vector<int> parseNumrange(char const* range, int max,
  267 + bool throw_error = false);
  268 +
  269 + static char const* help;
  270 +
  271 + int argc;
  272 + char** argv;
  273 + Options& o;
  274 +
  275 + std::vector<PointerHolder<char> > new_argv;
  276 + PointerHolder<char*> argv_ph;
  277 +};
  278 +
  279 +ArgParser::ArgParser(int argc, char* argv[], Options& o) :
  280 + argc(argc),
  281 + argv(argv),
  282 + o(o)
  283 +{
  284 +}
  285 +
  286 +void
  287 +ArgParser::handleArgFileArguments()
  288 +{
  289 + // Support reading arguments from files. Create a new argv. Ensure
  290 + // that argv itself as well as all its contents are automatically
  291 + // deleted by using PointerHolder objects to back the pointers in
  292 + // argv.
  293 + new_argv.push_back(PointerHolder<char>(true, QUtil::copy_string(argv[0])));
  294 + for (int i = 1; i < argc; ++i)
  295 + {
  296 + if ((strlen(argv[i]) > 1) && (argv[i][0] == '@'))
  297 + {
  298 + readArgsFromFile(1+argv[i]);
  299 + }
  300 + else
  301 + {
  302 + new_argv.push_back(
  303 + PointerHolder<char>(true, QUtil::copy_string(argv[i])));
  304 + }
  305 + }
  306 + argv_ph = PointerHolder<char*>(true, new char*[1+new_argv.size()]);
  307 + argv = argv_ph.getPointer();
  308 + for (size_t i = 0; i < new_argv.size(); ++i)
  309 + {
  310 + argv[i] = new_argv.at(i).getPointer();
  311 + }
  312 + argc = static_cast<int>(new_argv.size());
  313 + argv[argc] = 0;
  314 +}
  315 +
244 316 // Note: let's not be too noisy about documenting the fact that this
245 317 // software purposely fails to enforce the distinction between user
246 318 // and owner passwords. A user password is sufficient to gain full
... ... @@ -250,7 +322,7 @@ ProgressReporter::reportProgress(int percentage)
250 322 // (Setting this value requires the owner password.) The
251 323 // documentation discusses this as well.
252 324  
253   -static char const* help = "\
  325 +char const* ArgParser::help = "\
254 326 \n\
255 327 Usage: qpdf [ options ] { infilename | --empty } [ outfilename ]\n\
256 328 \n\
... ... @@ -540,7 +612,7 @@ exits with a status of 3. If warnings would have been issued but --no-warn\n\
540 612 was given, an exit status of 3 is still used.\n\
541 613 \n";
542 614  
543   -void usage(std::string const& msg)
  615 +void usageExit(std::string const& msg)
544 616 {
545 617 std::cerr
546 618 << std::endl
... ... @@ -552,6 +624,12 @@ void usage(std::string const&amp; msg)
552 624 exit(EXIT_ERROR);
553 625 }
554 626  
  627 +void
  628 +ArgParser::usage(std::string const& message)
  629 +{
  630 + usageExit(message);
  631 +}
  632 +
555 633 static JSON json_schema()
556 634 {
557 635 // This JSON object doubles as a schema and as documentation for
... ... @@ -707,8 +785,8 @@ static void show_encryption(QPDF&amp; pdf, Options&amp; o)
707 785 }
708 786 }
709 787  
710   -static std::vector<int> parse_numrange(char const* range, int max,
711   - bool throw_error = false)
  788 +std::vector<int>
  789 +ArgParser::parseNumrange(char const* range, int max, bool throw_error)
712 790 {
713 791 try
714 792 {
... ... @@ -728,35 +806,28 @@ static std::vector&lt;int&gt; parse_numrange(char const* range, int max,
728 806 return std::vector<int>();
729 807 }
730 808  
731   -static void
732   -parse_encrypt_options(
733   - int argc, char* argv[], int& cur_arg,
734   - std::string& user_password, std::string& owner_password, int& keylen,
735   - bool& r2_print, bool& r2_modify, bool& r2_extract, bool& r2_annotate,
736   - bool& r3_accessibility, bool& r3_extract,
737   - qpdf_r3_print_e& r3_print, qpdf_r3_modify_e& r3_modify,
738   - bool& force_V4, bool& cleartext_metadata, bool& use_aes,
739   - bool& force_R5)
  809 +void
  810 +ArgParser::parseEncryptOptions(int& cur_arg)
740 811 {
741 812 if (cur_arg + 3 >= argc)
742 813 {
743 814 usage("insufficient arguments to --encrypt");
744 815 }
745   - user_password = argv[cur_arg++];
746   - owner_password = argv[cur_arg++];
  816 + o.user_password = argv[cur_arg++];
  817 + o.owner_password = argv[cur_arg++];
747 818 std::string len_str = argv[cur_arg++];
748 819 if (len_str == "40")
749 820 {
750   - keylen = 40;
  821 + o.keylen = 40;
751 822 }
752 823 else if (len_str == "128")
753 824 {
754   - keylen = 128;
  825 + o.keylen = 128;
755 826 }
756 827 else if (len_str == "256")
757 828 {
758   - keylen = 256;
759   - use_aes = true;
  829 + o.keylen = 256;
  830 + o.use_aes = true;
760 831 }
761 832 else
762 833 {
... ... @@ -798,15 +869,15 @@ parse_encrypt_options(
798 869 usage("--print must be given as --print=option");
799 870 }
800 871 std::string val = parameter;
801   - if (keylen == 40)
  872 + if (o.keylen == 40)
802 873 {
803 874 if (val == "y")
804 875 {
805   - r2_print = true;
  876 + o.r2_print = true;
806 877 }
807 878 else if (val == "n")
808 879 {
809   - r2_print = false;
  880 + o.r2_print = false;
810 881 }
811 882 else
812 883 {
... ... @@ -817,15 +888,15 @@ parse_encrypt_options(
817 888 {
818 889 if (val == "full")
819 890 {
820   - r3_print = qpdf_r3p_full;
  891 + o.r3_print = qpdf_r3p_full;
821 892 }
822 893 else if (val == "low")
823 894 {
824   - r3_print = qpdf_r3p_low;
  895 + o.r3_print = qpdf_r3p_low;
825 896 }
826 897 else if (val == "none")
827 898 {
828   - r3_print = qpdf_r3p_none;
  899 + o.r3_print = qpdf_r3p_none;
829 900 }
830 901 else
831 902 {
... ... @@ -840,15 +911,15 @@ parse_encrypt_options(
840 911 usage("--modify must be given as --modify=option");
841 912 }
842 913 std::string val = parameter;
843   - if (keylen == 40)
  914 + if (o.keylen == 40)
844 915 {
845 916 if (val == "y")
846 917 {
847   - r2_modify = true;
  918 + o.r2_modify = true;
848 919 }
849 920 else if (val == "n")
850 921 {
851   - r2_modify = false;
  922 + o.r2_modify = false;
852 923 }
853 924 else
854 925 {
... ... @@ -859,23 +930,23 @@ parse_encrypt_options(
859 930 {
860 931 if (val == "all")
861 932 {
862   - r3_modify = qpdf_r3m_all;
  933 + o.r3_modify = qpdf_r3m_all;
863 934 }
864 935 else if (val == "annotate")
865 936 {
866   - r3_modify = qpdf_r3m_annotate;
  937 + o.r3_modify = qpdf_r3m_annotate;
867 938 }
868 939 else if (val == "form")
869 940 {
870   - r3_modify = qpdf_r3m_form;
  941 + o.r3_modify = qpdf_r3m_form;
871 942 }
872 943 else if (val == "assembly")
873 944 {
874   - r3_modify = qpdf_r3m_assembly;
  945 + o.r3_modify = qpdf_r3m_assembly;
875 946 }
876 947 else if (val == "none")
877 948 {
878   - r3_modify = qpdf_r3m_none;
  949 + o.r3_modify = qpdf_r3m_none;
879 950 }
880 951 else
881 952 {
... ... @@ -903,13 +974,13 @@ parse_encrypt_options(
903 974 {
904 975 usage("invalid -extract parameter");
905 976 }
906   - if (keylen == 40)
  977 + if (o.keylen == 40)
907 978 {
908   - r2_extract = result;
  979 + o.r2_extract = result;
909 980 }
910 981 else
911 982 {
912   - r3_extract = result;
  983 + o.r3_extract = result;
913 984 }
914 985 }
915 986 else if (strcmp(arg, "annotate") == 0)
... ... @@ -932,9 +1003,9 @@ parse_encrypt_options(
932 1003 {
933 1004 usage("invalid -annotate parameter");
934 1005 }
935   - if (keylen == 40)
  1006 + if (o.keylen == 40)
936 1007 {
937   - r2_annotate = result;
  1008 + o.r2_annotate = result;
938 1009 }
939 1010 else
940 1011 {
... ... @@ -962,13 +1033,13 @@ parse_encrypt_options(
962 1033 {
963 1034 usage("invalid -accessibility parameter");
964 1035 }
965   - if (keylen == 40)
  1036 + if (o.keylen == 40)
966 1037 {
967 1038 usage("-accessibility invalid for 40-bit keys");
968 1039 }
969 1040 else
970 1041 {
971   - r3_accessibility = result;
  1042 + o.r3_accessibility = result;
972 1043 }
973 1044 }
974 1045 else if (strcmp(arg, "cleartext-metadata") == 0)
... ... @@ -977,13 +1048,13 @@ parse_encrypt_options(
977 1048 {
978 1049 usage("--cleartext-metadata does not take a parameter");
979 1050 }
980   - if (keylen == 40)
  1051 + if (o.keylen == 40)
981 1052 {
982 1053 usage("--cleartext-metadata is invalid for 40-bit keys");
983 1054 }
984 1055 else
985 1056 {
986   - cleartext_metadata = true;
  1057 + o.cleartext_metadata = true;
987 1058 }
988 1059 }
989 1060 else if (strcmp(arg, "force-V4") == 0)
... ... @@ -992,13 +1063,13 @@ parse_encrypt_options(
992 1063 {
993 1064 usage("--force-V4 does not take a parameter");
994 1065 }
995   - if (keylen != 128)
  1066 + if (o.keylen != 128)
996 1067 {
997 1068 usage("--force-V4 is invalid only for 128-bit keys");
998 1069 }
999 1070 else
1000 1071 {
1001   - force_V4 = true;
  1072 + o.force_V4 = true;
1002 1073 }
1003 1074 }
1004 1075 else if (strcmp(arg, "force-R5") == 0)
... ... @@ -1007,13 +1078,13 @@ parse_encrypt_options(
1007 1078 {
1008 1079 usage("--force-R5 does not take a parameter");
1009 1080 }
1010   - if (keylen != 256)
  1081 + if (o.keylen != 256)
1011 1082 {
1012 1083 usage("--force-R5 is invalid only for 256-bit keys");
1013 1084 }
1014 1085 else
1015 1086 {
1016   - force_R5 = true;
  1087 + o.force_R5 = true;
1017 1088 }
1018 1089 }
1019 1090 else if (strcmp(arg, "use-aes") == 0)
... ... @@ -1036,11 +1107,11 @@ parse_encrypt_options(
1036 1107 {
1037 1108 usage("invalid -use-aes parameter");
1038 1109 }
1039   - if ((keylen == 40) && result)
  1110 + if ((o.keylen == 40) && result)
1040 1111 {
1041 1112 usage("use-aes is invalid for 40-bit keys");
1042 1113 }
1043   - else if ((keylen == 256) && (! result))
  1114 + else if ((o.keylen == 256) && (! result))
1044 1115 {
1045 1116 // qpdf would happily create files encrypted with RC4
1046 1117 // using /V=5, but Adobe reader can't read them.
... ... @@ -1048,7 +1119,7 @@ parse_encrypt_options(
1048 1119 }
1049 1120 else
1050 1121 {
1051   - use_aes = result;
  1122 + o.use_aes = result;
1052 1123 }
1053 1124 }
1054 1125 else
... ... @@ -1058,9 +1129,8 @@ parse_encrypt_options(
1058 1129 }
1059 1130 }
1060 1131  
1061   -static std::vector<PageSpec>
1062   -parse_pages_options(
1063   - int argc, char* argv[], int& cur_arg)
  1132 +std::vector<PageSpec>
  1133 +ArgParser::parsePagesOptions(int& cur_arg)
1064 1134 {
1065 1135 std::vector<PageSpec> result;
1066 1136 while (1)
... ... @@ -1100,7 +1170,7 @@ parse_pages_options(
1100 1170 {
1101 1171 try
1102 1172 {
1103   - parse_numrange(range, 0, true);
  1173 + parseNumrange(range, 0, true);
1104 1174 }
1105 1175 catch (std::runtime_error& e1)
1106 1176 {
... ... @@ -1112,10 +1182,10 @@ parse_pages_options(
1112 1182 QTC::TC("qpdf", "qpdf pages range omitted in middle");
1113 1183 range_omitted = true;
1114 1184 }
1115   - catch (std::runtime_error& e2)
  1185 + catch (std::runtime_error&)
1116 1186 {
1117   - // Ignore. The range is invalid and not a file.
1118   - // We'll get an error message later.
  1187 + // Give the range error
  1188 + usage(e1.what());
1119 1189 }
1120 1190 }
1121 1191 }
... ... @@ -1137,7 +1207,15 @@ QPDFPageData::QPDFPageData(std::string const&amp; filename,
1137 1207 qpdf(qpdf),
1138 1208 orig_pages(qpdf->getAllPages())
1139 1209 {
1140   - this->selected_pages = parse_numrange(range, this->orig_pages.size());
  1210 + try
  1211 + {
  1212 + this->selected_pages =
  1213 + QUtil::parse_numrange(range, this->orig_pages.size());
  1214 + }
  1215 + catch (std::runtime_error& e)
  1216 + {
  1217 + usageExit("parsing numeric range for " + filename + ": " + e.what());
  1218 + }
1141 1219 }
1142 1220  
1143 1221 static void parse_version(std::string const& full_version_string,
... ... @@ -1155,8 +1233,8 @@ static void parse_version(std::string const&amp; full_version_string,
1155 1233 version = v;
1156 1234 }
1157 1235  
1158   -static void read_args_from_file(char const* filename,
1159   - std::vector<PointerHolder<char> >& new_argv)
  1236 +void
  1237 +ArgParser::readArgsFromFile(char const* filename)
1160 1238 {
1161 1239 std::list<std::string> lines;
1162 1240 if (strcmp(filename, "-") == 0)
... ... @@ -1177,7 +1255,8 @@ static void read_args_from_file(char const* filename,
1177 1255 }
1178 1256 }
1179 1257  
1180   -static void handle_help_version(int argc, char* argv[])
  1258 +void
  1259 +ArgParser::handleHelpVersion()
1181 1260 {
1182 1261 // Make sure the output looks right on an 80-column display.
1183 1262  
... ... @@ -1244,7 +1323,8 @@ static void handle_help_version(int argc, char* argv[])
1244 1323 }
1245 1324 }
1246 1325  
1247   -static void parse_rotation_parameter(Options& o, std::string const& parameter)
  1326 +void
  1327 +ArgParser::parseRotationParameter(std::string const& parameter)
1248 1328 {
1249 1329 std::string angle_str;
1250 1330 std::string range;
... ... @@ -1285,7 +1365,7 @@ static void parse_rotation_parameter(Options&amp; o, std::string const&amp; parameter)
1285 1365 bool range_valid = false;
1286 1366 try
1287 1367 {
1288   - parse_numrange(range.c_str(), 0, true);
  1368 + parseNumrange(range.c_str(), 0, true);
1289 1369 range_valid = true;
1290 1370 }
1291 1371 catch (std::runtime_error const&)
... ... @@ -1308,8 +1388,11 @@ static void parse_rotation_parameter(Options&amp; o, std::string const&amp; parameter)
1308 1388 }
1309 1389 }
1310 1390  
1311   -static void parse_options(int argc, char* argv[], Options& o)
  1391 +void
  1392 +ArgParser::parseOptions()
1312 1393 {
  1394 + handleHelpVersion();
  1395 + handleArgFileArguments();
1313 1396 for (int i = 1; i < argc; ++i)
1314 1397 {
1315 1398 char const* arg = argv[i];
... ... @@ -1345,12 +1428,7 @@ static void parse_options(int argc, char* argv[], Options&amp; o)
1345 1428 }
1346 1429 else if (strcmp(arg, "encrypt") == 0)
1347 1430 {
1348   - parse_encrypt_options(
1349   - argc, argv, ++i,
1350   - o.user_password, o.owner_password, o.keylen,
1351   - o.r2_print, o.r2_modify, o.r2_extract, o.r2_annotate,
1352   - o.r3_accessibility, o.r3_extract, o.r3_print, o.r3_modify,
1353   - o.force_V4, o.cleartext_metadata, o.use_aes, o.force_R5);
  1431 + parseEncryptOptions(++i);
1354 1432 o.encrypt = true;
1355 1433 o.decrypt = false;
1356 1434 o.copy_encryption = false;
... ... @@ -1388,7 +1466,7 @@ static void parse_options(int argc, char* argv[], Options&amp; o)
1388 1466 }
1389 1467 else if (strcmp(arg, "pages") == 0)
1390 1468 {
1391   - o.page_specs = parse_pages_options(argc, argv, ++i);
  1469 + o.page_specs = parsePagesOptions(++i);
1392 1470 if (o.page_specs.empty())
1393 1471 {
1394 1472 usage("--pages: no page specifications given");
... ... @@ -1401,7 +1479,7 @@ static void parse_options(int argc, char* argv[], Options&amp; o)
1401 1479 usage("--rotate must be given as"
1402 1480 " --rotate=[+|-]angle:page-range");
1403 1481 }
1404   - parse_rotation_parameter(o, parameter);
  1482 + parseRotationParameter(parameter);
1405 1483 }
1406 1484 else if (strcmp(arg, "stream-data") == 0)
1407 1485 {
... ... @@ -2436,7 +2514,9 @@ static void handle_rotations(QPDF&amp; pdf, Options&amp; o)
2436 2514 {
2437 2515 std::string const& range = (*iter).first;
2438 2516 RotationSpec const& rspec = (*iter).second;
2439   - std::vector<int> to_rotate = parse_numrange(range.c_str(), npages);
  2517 + // range has been previously validated
  2518 + std::vector<int> to_rotate =
  2519 + QUtil::parse_numrange(range.c_str(), npages);
2440 2520 for (std::vector<int>::iterator i2 = to_rotate.begin();
2441 2521 i2 != to_rotate.end(); ++i2)
2442 2522 {
... ... @@ -2741,43 +2821,17 @@ int main(int argc, char* argv[])
2741 2821 whoami = QUtil::getWhoami(argv[0]);
2742 2822 QUtil::setLineBuf(stdout);
2743 2823  
2744   - // For libtool's sake....
  2824 + // Remove prefix added by libtool for consistency during testing.
2745 2825 if (strncmp(whoami, "lt-", 3) == 0)
2746 2826 {
2747 2827 whoami += 3;
2748 2828 }
2749 2829  
2750   - handle_help_version(argc, argv);
2751   -
2752   - // Support reading arguments from files. Create a new argv. Ensure
2753   - // that argv itself as well as all its contents are automatically
2754   - // deleted by using PointerHolder objects to back the pointers in
2755   - // argv.
2756   - std::vector<PointerHolder<char> > new_argv;
2757   - new_argv.push_back(PointerHolder<char>(true, QUtil::copy_string(argv[0])));
2758   - for (int i = 1; i < argc; ++i)
2759   - {
2760   - if ((strlen(argv[i]) > 1) && (argv[i][0] == '@'))
2761   - {
2762   - read_args_from_file(1+argv[i], new_argv);
2763   - }
2764   - else
2765   - {
2766   - new_argv.push_back(
2767   - PointerHolder<char>(true, QUtil::copy_string(argv[i])));
2768   - }
2769   - }
2770   - PointerHolder<char*> argv_ph(true, new char*[1+new_argv.size()]);
2771   - argv = argv_ph.getPointer();
2772   - for (size_t i = 0; i < new_argv.size(); ++i)
2773   - {
2774   - argv[i] = new_argv.at(i).getPointer();
2775   - }
2776   - argc = static_cast<int>(new_argv.size());
2777   - argv[argc] = 0;
2778   -
  2830 + // ArgParser must stay in scope for the duration of qpdf's run as
  2831 + // it holds dynamic memory used for argv.
2779 2832 Options o;
2780   - parse_options(argc, argv, o);
  2833 + ArgParser ap(argc, argv, o);
  2834 + ap.parseOptions();
2781 2835  
2782 2836 try
2783 2837 {
... ...