Commit 7ed991343b0d6d4f4ec84cf41dde60debffc4074
1 parent
f545f8b0
Better diagnostics when --pages is not closed (fixes #555)
Showing
4 changed files
with
71 additions
and
24 deletions
ChangeLog
qpdf/qpdf.cc
| ... | ... | @@ -738,6 +738,21 @@ static void parse_object_id(std::string const& objspec, |
| 738 | 738 | } |
| 739 | 739 | } |
| 740 | 740 | |
| 741 | +static bool file_exists(char const* filename) | |
| 742 | +{ | |
| 743 | + try | |
| 744 | + { | |
| 745 | + fclose(QUtil::safe_fopen(filename, "rb")); | |
| 746 | + return true; | |
| 747 | + } | |
| 748 | + catch (std::runtime_error&) | |
| 749 | + { | |
| 750 | + // can't open the file | |
| 751 | + } | |
| 752 | + return false; | |
| 753 | +} | |
| 754 | + | |
| 755 | + | |
| 741 | 756 | // This is not a general-purpose argument parser. It is tightly |
| 742 | 757 | // crafted to work with qpdf. qpdf's command-line syntax is very |
| 743 | 758 | // complex because of its long history, and it doesn't really follow |
| ... | ... | @@ -2080,6 +2095,10 @@ void |
| 2080 | 2095 | ArgParser::argPages() |
| 2081 | 2096 | { |
| 2082 | 2097 | ++cur_arg; |
| 2098 | + if (! o.page_specs.empty()) | |
| 2099 | + { | |
| 2100 | + usage("the --pages may only be specified one time"); | |
| 2101 | + } | |
| 2083 | 2102 | o.page_specs = parsePagesOptions(); |
| 2084 | 2103 | if (o.page_specs.empty()) |
| 2085 | 2104 | { |
| ... | ... | @@ -2937,19 +2956,15 @@ ArgParser::handleArgFileArguments() |
| 2937 | 2956 | char* argfile = 0; |
| 2938 | 2957 | if ((strlen(argv[i]) > 1) && (argv[i][0] == '@')) |
| 2939 | 2958 | { |
| 2940 | - try | |
| 2959 | + argfile = 1 + argv[i]; | |
| 2960 | + if (strcmp(argfile, "-") != 0) | |
| 2941 | 2961 | { |
| 2942 | - argfile = 1 + argv[i]; | |
| 2943 | - if (strcmp(argfile, "-") != 0) | |
| 2962 | + if (! file_exists(argfile)) | |
| 2944 | 2963 | { |
| 2945 | - fclose(QUtil::safe_fopen(argfile, "rb")); | |
| 2964 | + // The file's not there; treating as regular option | |
| 2965 | + argfile = nullptr; | |
| 2946 | 2966 | } |
| 2947 | 2967 | } |
| 2948 | - catch (std::runtime_error&) | |
| 2949 | - { | |
| 2950 | - // The file's not there; treating as regular option | |
| 2951 | - argfile = 0; | |
| 2952 | - } | |
| 2953 | 2968 | } |
| 2954 | 2969 | if (argfile) |
| 2955 | 2970 | { |
| ... | ... | @@ -3220,6 +3235,16 @@ ArgParser::parseNumrange(char const* range, int max, bool throw_error) |
| 3220 | 3235 | std::vector<PageSpec> |
| 3221 | 3236 | ArgParser::parsePagesOptions() |
| 3222 | 3237 | { |
| 3238 | + auto check_unclosed = [this](char const* arg, int n) { | |
| 3239 | + if ((strlen(arg) > 0) && (arg[0] == '-')) | |
| 3240 | + { | |
| 3241 | + // A common error is to forget to close --pages with --, | |
| 3242 | + // so catch this as special case | |
| 3243 | + QTC::TC("qpdf", "check unclosed --pages", n); | |
| 3244 | + usage("the --pages option must be terminated with -- by itself"); | |
| 3245 | + } | |
| 3246 | + }; | |
| 3247 | + | |
| 3223 | 3248 | std::vector<PageSpec> result; |
| 3224 | 3249 | while (1) |
| 3225 | 3250 | { |
| ... | ... | @@ -3234,6 +3259,10 @@ ArgParser::parsePagesOptions() |
| 3234 | 3259 | char const* file = argv[cur_arg++]; |
| 3235 | 3260 | char const* password = 0; |
| 3236 | 3261 | char const* range = argv[cur_arg++]; |
| 3262 | + if (! file_exists(file)) | |
| 3263 | + { | |
| 3264 | + check_unclosed(file, 0); | |
| 3265 | + } | |
| 3237 | 3266 | if (strncmp(range, "--password=", 11) == 0) |
| 3238 | 3267 | { |
| 3239 | 3268 | // Oh, that's the password, not the range |
| ... | ... | @@ -3263,23 +3292,20 @@ ArgParser::parsePagesOptions() |
| 3263 | 3292 | catch (std::runtime_error& e1) |
| 3264 | 3293 | { |
| 3265 | 3294 | // The range is invalid. Let's see if it's a file. |
| 3266 | - try | |
| 3295 | + range_omitted = true; | |
| 3296 | + if (strcmp(range, ".") == 0) | |
| 3267 | 3297 | { |
| 3268 | - if (strcmp(range, ".") == 0) | |
| 3269 | - { | |
| 3270 | - // "." means the input file. | |
| 3271 | - QTC::TC("qpdf", "qpdf pages range omitted with ."); | |
| 3272 | - } | |
| 3273 | - else | |
| 3274 | - { | |
| 3275 | - fclose(QUtil::safe_fopen(range, "rb")); | |
| 3276 | - QTC::TC("qpdf", "qpdf pages range omitted in middle"); | |
| 3277 | - // Yup, it's a file. | |
| 3278 | - } | |
| 3279 | - range_omitted = true; | |
| 3298 | + // "." means the input file. | |
| 3299 | + QTC::TC("qpdf", "qpdf pages range omitted with ."); | |
| 3280 | 3300 | } |
| 3281 | - catch (std::runtime_error&) | |
| 3301 | + else if (file_exists(range)) | |
| 3302 | + { | |
| 3303 | + QTC::TC("qpdf", "qpdf pages range omitted in middle"); | |
| 3304 | + // Yup, it's a file. | |
| 3305 | + } | |
| 3306 | + else | |
| 3282 | 3307 | { |
| 3308 | + check_unclosed(range, 1); | |
| 3283 | 3309 | // Give the range error |
| 3284 | 3310 | usage(e1.what()); |
| 3285 | 3311 | } | ... | ... |
qpdf/qpdf.testcov
qpdf/qtest/qpdf.test
| ... | ... | @@ -139,7 +139,7 @@ foreach my $c (@completion_tests) |
| 139 | 139 | show_ntests(); |
| 140 | 140 | # ---------- |
| 141 | 141 | $td->notify("--- Argument Parsing ---"); |
| 142 | -$n_tests += 6; | |
| 142 | +$n_tests += 9; | |
| 143 | 143 | |
| 144 | 144 | $td->runtest("required argument", |
| 145 | 145 | {$td->COMMAND => "qpdf --password minimal.pdf"}, |
| ... | ... | @@ -171,6 +171,21 @@ $td->runtest("extra overlay filename", |
| 171 | 171 | {$td->REGEXP => ".*overlay file already specified.*", |
| 172 | 172 | $td->EXIT_STATUS => 2}, |
| 173 | 173 | $td->NORMALIZE_NEWLINES); |
| 174 | +$td->runtest("multiple pages options", | |
| 175 | + {$td->COMMAND => "qpdf --pages . -- --pages . --"}, | |
| 176 | + {$td->REGEXP => ".*--pages may only be specified one time.*", | |
| 177 | + $td->EXIT_STATUS => 2}, | |
| 178 | + $td->NORMALIZE_NEWLINES); | |
| 179 | +$td->runtest("bad numeric range detects unclosed --pages", | |
| 180 | + {$td->COMMAND => "qpdf --pages . --pages . --"}, | |
| 181 | + {$td->REGEXP => ".*--pages option must be terminated with --.*", | |
| 182 | + $td->EXIT_STATUS => 2}, | |
| 183 | + $td->NORMALIZE_NEWLINES); | |
| 184 | +$td->runtest("bad file detected as unclosed --pages", | |
| 185 | + {$td->COMMAND => "qpdf --pages . 1 --xyz out"}, | |
| 186 | + {$td->REGEXP => ".*--pages option must be terminated with --.*", | |
| 187 | + $td->EXIT_STATUS => 2}, | |
| 188 | + $td->NORMALIZE_NEWLINES); | |
| 174 | 189 | |
| 175 | 190 | show_ntests(); |
| 176 | 191 | # ---------- | ... | ... |