2
1
mirror of https://github.com/qpdf/qpdf.git synced 2024-11-01 03:12:29 +00:00

Better diagnostics when --pages is not closed (fixes #555)

This commit is contained in:
Jay Berkenbilt 2021-11-02 16:22:37 -04:00
parent f545f8b076
commit 7ed991343b
4 changed files with 71 additions and 24 deletions

View File

@ -1,3 +1,8 @@
2021-11-02 Jay Berkenbilt <ejb@ql.org>
* Improve error reporting when someone forgets the -- after
--pages. Fixes #555.
2021-05-12 Jay Berkenbilt <ejb@ql.org> 2021-05-12 Jay Berkenbilt <ejb@ql.org>
* Bug fix: ensure we don't overflow any string bounds while * Bug fix: ensure we don't overflow any string bounds while

View File

@ -738,6 +738,21 @@ static void parse_object_id(std::string const& objspec,
} }
} }
static bool file_exists(char const* filename)
{
try
{
fclose(QUtil::safe_fopen(filename, "rb"));
return true;
}
catch (std::runtime_error&)
{
// can't open the file
}
return false;
}
// This is not a general-purpose argument parser. It is tightly // This is not a general-purpose argument parser. It is tightly
// crafted to work with qpdf. qpdf's command-line syntax is very // crafted to work with qpdf. qpdf's command-line syntax is very
// complex because of its long history, and it doesn't really follow // complex because of its long history, and it doesn't really follow
@ -2080,6 +2095,10 @@ void
ArgParser::argPages() ArgParser::argPages()
{ {
++cur_arg; ++cur_arg;
if (! o.page_specs.empty())
{
usage("the --pages may only be specified one time");
}
o.page_specs = parsePagesOptions(); o.page_specs = parsePagesOptions();
if (o.page_specs.empty()) if (o.page_specs.empty())
{ {
@ -2937,19 +2956,15 @@ ArgParser::handleArgFileArguments()
char* argfile = 0; char* argfile = 0;
if ((strlen(argv[i]) > 1) && (argv[i][0] == '@')) if ((strlen(argv[i]) > 1) && (argv[i][0] == '@'))
{ {
try argfile = 1 + argv[i];
if (strcmp(argfile, "-") != 0)
{ {
argfile = 1 + argv[i]; if (! file_exists(argfile))
if (strcmp(argfile, "-") != 0)
{ {
fclose(QUtil::safe_fopen(argfile, "rb")); // The file's not there; treating as regular option
argfile = nullptr;
} }
} }
catch (std::runtime_error&)
{
// The file's not there; treating as regular option
argfile = 0;
}
} }
if (argfile) if (argfile)
{ {
@ -3220,6 +3235,16 @@ ArgParser::parseNumrange(char const* range, int max, bool throw_error)
std::vector<PageSpec> std::vector<PageSpec>
ArgParser::parsePagesOptions() ArgParser::parsePagesOptions()
{ {
auto check_unclosed = [this](char const* arg, int n) {
if ((strlen(arg) > 0) && (arg[0] == '-'))
{
// A common error is to forget to close --pages with --,
// so catch this as special case
QTC::TC("qpdf", "check unclosed --pages", n);
usage("the --pages option must be terminated with -- by itself");
}
};
std::vector<PageSpec> result; std::vector<PageSpec> result;
while (1) while (1)
{ {
@ -3234,6 +3259,10 @@ ArgParser::parsePagesOptions()
char const* file = argv[cur_arg++]; char const* file = argv[cur_arg++];
char const* password = 0; char const* password = 0;
char const* range = argv[cur_arg++]; char const* range = argv[cur_arg++];
if (! file_exists(file))
{
check_unclosed(file, 0);
}
if (strncmp(range, "--password=", 11) == 0) if (strncmp(range, "--password=", 11) == 0)
{ {
// Oh, that's the password, not the range // Oh, that's the password, not the range
@ -3263,23 +3292,20 @@ ArgParser::parsePagesOptions()
catch (std::runtime_error& e1) catch (std::runtime_error& e1)
{ {
// The range is invalid. Let's see if it's a file. // The range is invalid. Let's see if it's a file.
try range_omitted = true;
if (strcmp(range, ".") == 0)
{ {
if (strcmp(range, ".") == 0) // "." means the input file.
{ QTC::TC("qpdf", "qpdf pages range omitted with .");
// "." means the input file.
QTC::TC("qpdf", "qpdf pages range omitted with .");
}
else
{
fclose(QUtil::safe_fopen(range, "rb"));
QTC::TC("qpdf", "qpdf pages range omitted in middle");
// Yup, it's a file.
}
range_omitted = true;
} }
catch (std::runtime_error&) else if (file_exists(range))
{ {
QTC::TC("qpdf", "qpdf pages range omitted in middle");
// Yup, it's a file.
}
else
{
check_unclosed(range, 1);
// Give the range error // Give the range error
usage(e1.what()); usage(e1.what());
} }

View File

@ -594,3 +594,4 @@ qpdf copy fields non-first from orig 0
QPDF resolve duplicated page in insert 0 QPDF resolve duplicated page in insert 0
QPDFWriter preserve object streams 1 QPDFWriter preserve object streams 1
QPDFWriter exclude from object stream 0 QPDFWriter exclude from object stream 0
check unclosed --pages 1

View File

@ -139,7 +139,7 @@ foreach my $c (@completion_tests)
show_ntests(); show_ntests();
# ---------- # ----------
$td->notify("--- Argument Parsing ---"); $td->notify("--- Argument Parsing ---");
$n_tests += 6; $n_tests += 9;
$td->runtest("required argument", $td->runtest("required argument",
{$td->COMMAND => "qpdf --password minimal.pdf"}, {$td->COMMAND => "qpdf --password minimal.pdf"},
@ -171,6 +171,21 @@ $td->runtest("extra overlay filename",
{$td->REGEXP => ".*overlay file already specified.*", {$td->REGEXP => ".*overlay file already specified.*",
$td->EXIT_STATUS => 2}, $td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES); $td->NORMALIZE_NEWLINES);
$td->runtest("multiple pages options",
{$td->COMMAND => "qpdf --pages . -- --pages . --"},
{$td->REGEXP => ".*--pages may only be specified one time.*",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("bad numeric range detects unclosed --pages",
{$td->COMMAND => "qpdf --pages . --pages . --"},
{$td->REGEXP => ".*--pages option must be terminated with --.*",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
$td->runtest("bad file detected as unclosed --pages",
{$td->COMMAND => "qpdf --pages . 1 --xyz out"},
{$td->REGEXP => ".*--pages option must be terminated with --.*",
$td->EXIT_STATUS => 2},
$td->NORMALIZE_NEWLINES);
show_ntests(); show_ntests();
# ---------- # ----------