mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-22 10:58:58 +00:00
Add --is-encrypted and --requires-password (fixes #390)
Allow exit status-based checking of whether a file is encrypted or requires a password without necessarily supplying the correct password. Useful for scripting.
This commit is contained in:
parent
8b1c4828b7
commit
731c4f711b
10
ChangeLog
10
ChangeLog
@ -1,3 +1,13 @@
|
||||
2020-01-26 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Add options --is-encrypted and --requires-password. These can be
|
||||
used with files, including encrypted files with unknown passwords,
|
||||
to determine whether or not a file is encrypted and whether a
|
||||
password is required to open the file. The --requires-password
|
||||
option can also be used to determine whether a supplied password
|
||||
is correct. Information is supplied through exit codes, making
|
||||
these options particularly useful for shell scripts. Fixes #390.
|
||||
|
||||
2020-01-14 Jay Berkenbilt <ejb@ql.org>
|
||||
|
||||
* Fix for Windows being unable to acquire crypt context with a new
|
||||
|
@ -665,6 +665,40 @@ make
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--is-encrypted</option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Silently exit with status 0 if the file is encrypted or status
|
||||
2 if the file is not encrypted. This is useful for shell
|
||||
scripts. Other options are ignored if this is given. This
|
||||
option is mutually exclusive with
|
||||
<option>--requires-password</option>. Both this option and
|
||||
<option>--requires-password</option> exit with status 2 for
|
||||
non-encrypted files.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--requires-password</option></term>
|
||||
<listitem>
|
||||
<para>
|
||||
Silently exit with status 0 if a password (other than as
|
||||
supplied) is required. Exit with status 2 if the file is not
|
||||
encrypted. Exit with status 3 if the file is encrypted but
|
||||
requires no password or the correct password has been
|
||||
supplied. This is useful for shell scripts. Note that any
|
||||
supplied password is used when opening the file. When used
|
||||
with a <option>--password</option> option, this option can be
|
||||
used to check the correctness of the password. In that case,
|
||||
an exit status of 3 means the file works with the supplied
|
||||
password. This option is mutually exclusive with
|
||||
<option>--is-encrypted</option>. Both this option and
|
||||
<option>--is-encrypted</option> exit with status 2 for
|
||||
non-encrypted files.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
<varlistentry>
|
||||
<term><option>--verbose</option></term>
|
||||
<listitem>
|
||||
@ -4675,6 +4709,23 @@ print "\n";
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</listitem>
|
||||
<listitem>
|
||||
<para>
|
||||
CLI Enhancements
|
||||
</para>
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
Added options <option>--is-encrypted</option> and
|
||||
<option>--requires-password</option> for testing whether a
|
||||
file is encrypted or requires a password other than the
|
||||
supplied (or empty) password. These communicate via exit
|
||||
status, making them useful for shell scripts. They also work
|
||||
on encrypted files with unknown passwords.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
82
qpdf/qpdf.cc
82
qpdf/qpdf.cc
@ -29,8 +29,12 @@
|
||||
#include <qpdf/QPDFWriter.hh>
|
||||
#include <qpdf/QIntC.hh>
|
||||
|
||||
static int const EXIT_ERROR = 2;
|
||||
static int const EXIT_WARNING = 3;
|
||||
static int constexpr EXIT_ERROR = 2;
|
||||
static int constexpr EXIT_WARNING = 3;
|
||||
|
||||
// For is-encrypted and requires-password
|
||||
static int constexpr EXIT_IS_NOT_ENCRYPTED = 2;
|
||||
static int constexpr EXIT_CORRECT_PASSWORD = 3;
|
||||
|
||||
static char const* whoami = 0;
|
||||
|
||||
@ -183,6 +187,8 @@ struct Options
|
||||
under_overlay(0),
|
||||
require_outfile(true),
|
||||
replace_input(false),
|
||||
check_is_encrypted(false),
|
||||
check_requires_password(false),
|
||||
infilename(0),
|
||||
outfilename(0)
|
||||
{
|
||||
@ -287,6 +293,8 @@ struct Options
|
||||
std::map<std::string, RotationSpec> rotations;
|
||||
bool require_outfile;
|
||||
bool replace_input;
|
||||
bool check_is_encrypted;
|
||||
bool check_requires_password;
|
||||
char const* infilename;
|
||||
char const* outfilename;
|
||||
};
|
||||
@ -718,6 +726,8 @@ class ArgParser
|
||||
void argUOpassword(char* parameter);
|
||||
void argEndUnderOverlay();
|
||||
void argReplaceInput();
|
||||
void argIsEncrypted();
|
||||
void argRequiresPassword();
|
||||
|
||||
void usage(std::string const& message);
|
||||
void checkCompletion();
|
||||
@ -948,6 +958,8 @@ ArgParser::initOptionTable()
|
||||
(*t)["overlay"] = oe_bare(&ArgParser::argOverlay);
|
||||
(*t)["underlay"] = oe_bare(&ArgParser::argUnderlay);
|
||||
(*t)["replace-input"] = oe_bare(&ArgParser::argReplaceInput);
|
||||
(*t)["is-encrypted"] = oe_bare(&ArgParser::argIsEncrypted);
|
||||
(*t)["requires-password"] = oe_bare(&ArgParser::argRequiresPassword);
|
||||
|
||||
t = &this->encrypt40_option_table;
|
||||
(*t)["--"] = oe_bare(&ArgParser::argEndEncrypt);
|
||||
@ -1105,6 +1117,13 @@ ArgParser::argHelp()
|
||||
<< "--completion-bash output a bash complete command you can eval\n"
|
||||
<< "--completion-zsh output a zsh complete command you can eval\n"
|
||||
<< "--password=password specify a password for accessing encrypted files\n"
|
||||
<< "--is-encrypted silently exit 0 if the file is encrypted or 2\n"
|
||||
<< " if not; useful for shell scripts\n"
|
||||
<< "--requires-password silently exit 0 if a password (other than as\n"
|
||||
<< " supplied) is required, 2 if the file is not\n"
|
||||
<< " encrypted, or 3 if the file is encrypted\n"
|
||||
<< " but requires no password or the supplied password\n"
|
||||
<< " is correct; useful for shell scripts\n"
|
||||
<< "--verbose provide additional informational output\n"
|
||||
<< "--progress give progress indicators while writing output\n"
|
||||
<< "--no-warn suppress warnings\n"
|
||||
@ -2352,6 +2371,20 @@ ArgParser::argReplaceInput()
|
||||
o.replace_input = true;
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::argIsEncrypted()
|
||||
{
|
||||
o.check_is_encrypted = true;
|
||||
o.require_outfile = false;
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::argRequiresPassword()
|
||||
{
|
||||
o.check_requires_password = true;
|
||||
o.require_outfile = false;
|
||||
}
|
||||
|
||||
void
|
||||
ArgParser::handleArgFileArguments()
|
||||
{
|
||||
@ -3113,6 +3146,11 @@ ArgParser::doFinalChecks()
|
||||
{
|
||||
o.externalize_inline_images = true;
|
||||
}
|
||||
if (o.check_requires_password && o.check_is_encrypted)
|
||||
{
|
||||
usage("--requires-password and --is-encrypted may not be given"
|
||||
" together");
|
||||
}
|
||||
|
||||
if (o.require_outfile && o.outfilename &&
|
||||
(strcmp(o.outfilename, "-") == 0))
|
||||
@ -5252,9 +5290,45 @@ int realmain(int argc, char* argv[])
|
||||
try
|
||||
{
|
||||
ap.parseOptions();
|
||||
PointerHolder<QPDF> pdf_ph =
|
||||
process_file(o.infilename, o.password, o);
|
||||
PointerHolder<QPDF> pdf_ph;
|
||||
try
|
||||
{
|
||||
pdf_ph = process_file(o.infilename, o.password, o);
|
||||
}
|
||||
catch (QPDFExc& e)
|
||||
{
|
||||
if ((e.getErrorCode() == qpdf_e_password) &&
|
||||
(o.check_is_encrypted || o.check_requires_password))
|
||||
{
|
||||
// Allow --is-encrypted and --requires-password to
|
||||
// work when an incorrect password is supplied.
|
||||
exit(0);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
QPDF& pdf = *pdf_ph;
|
||||
if (o.check_is_encrypted)
|
||||
{
|
||||
if (pdf.isEncrypted())
|
||||
{
|
||||
exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
exit(EXIT_IS_NOT_ENCRYPTED);
|
||||
}
|
||||
}
|
||||
else if (o.check_requires_password)
|
||||
{
|
||||
if (pdf.isEncrypted())
|
||||
{
|
||||
exit(EXIT_CORRECT_PASSWORD);
|
||||
}
|
||||
else
|
||||
{
|
||||
exit(EXIT_IS_NOT_ENCRYPTED);
|
||||
}
|
||||
}
|
||||
if (! o.page_specs.empty())
|
||||
{
|
||||
handle_page_specs(pdf, o);
|
||||
|
@ -250,6 +250,28 @@ $td->runtest("check exception handling",
|
||||
{$td->FILE => "exceptions.out", $td->EXIT_STATUS => 0},
|
||||
$td->NORMALIZE_NEWLINES);
|
||||
|
||||
show_ntests();
|
||||
# ----------
|
||||
$td->notify("--- Check encryption/password ---");
|
||||
my @check_encryption_password = (
|
||||
# file, password, is-encrypted, requires-password
|
||||
["minimal.pdf", "", 2, 2],
|
||||
["20-pages.pdf", "", 0, 0],
|
||||
["20-pages.pdf", "user", 0, 3],
|
||||
);
|
||||
$n_tests += 2 * scalar(@check_encryption_password);
|
||||
foreach my $d (@check_encryption_password)
|
||||
{
|
||||
my ($file, $pass, $is_encrypted, $requires_password) = @$d;
|
||||
$td->runtest("is encrypted ($file, pass=$pass)",
|
||||
{$td->COMMAND => "qpdf --is-encrypted --password=$pass $file"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => $is_encrypted});
|
||||
$td->runtest("requires password ($file, pass=$pass)",
|
||||
{$td->COMMAND => "qpdf --requires-password" .
|
||||
" --password=$pass $file"},
|
||||
{$td->STRING => "", $td->EXIT_STATUS => $requires_password});
|
||||
}
|
||||
|
||||
show_ntests();
|
||||
# ----------
|
||||
$td->notify("--- Dangling Refs ---");
|
||||
|
Loading…
Reference in New Issue
Block a user