Commit 7d7e2234a537b6cd2205fb5cf942d5a9e8a866e3

Authored by Jay Berkenbilt
1 parent 1173a0bd

Implement new --encrypt args and completion (fixes #784)

Positional arguments are supported in a backward-compatible way, but
completion no longer guides users to it.
ChangeLog
1 1 2023-12-22 Jay Berkenbilt <ejb@ql.org>
2 2  
  3 + * Allow the syntax "--encrypt --user-password=user-password
  4 + --owner-password=owner-password --bits={40,128,256}" when
  5 + encrypting PDF files. This is an alternative to the syntax
  6 + "--encrypt user-password owner-password {40,128,256}", which will
  7 + continue to be supported. The new syntax works better with shell
  8 + completion and allows creation of passwords that start with "-".
  9 + Fixes #874.
  10 +
3 11 * When setting a check box value, allow any value other than /Off
4 12 to mean checked. This is permitted by the spec. Previously, any
5 13 value other than /Yes or /Off was rejected. Fixes #1056.
... ...
job.sums
... ... @@ -8,10 +8,10 @@ include/qpdf/auto_job_c_pages.hh b3cc0f21029f6d89efa043dcdbfa183cb59325b6506001c
8 8 include/qpdf/auto_job_c_uo.hh ae21b69a1efa9333050f4833d465f6daff87e5b38e5106e49bbef5d4132e4ed1
9 9 job.yml 4f89fc7b622df897d30d403d8035aa36fc7de8d8c43042c736e0300d904cb05c
10 10 libqpdf/qpdf/auto_job_decl.hh 9c6f701c29f3f764d620186bed92685a2edf2e4d11e4f4532862c05470cfc4d2
11   -libqpdf/qpdf/auto_job_help.hh 788320d439519ecd284621531e96ee698965a9ad342fd423c5fb1de75d2a06b1
  11 +libqpdf/qpdf/auto_job_help.hh ea1fdca2aa405bdf193732c5a2789c602efe2add3aa6e2dceecfacee175ce65c
12 12 libqpdf/qpdf/auto_job_init.hh b4c2b3724fba61f1206fd3bae81951636852592f67a63ef9539839c2c5995065
13 13 libqpdf/qpdf/auto_job_json_decl.hh 06caa46eaf71db8a50c046f91866baa8087745a9474319fb7c86d92634cc8297
14 14 libqpdf/qpdf/auto_job_json_init.hh f5acb9aa103131cb68dec0e12c4d237a6459bdb49b24773c24f0c2724a462b8f
15 15 libqpdf/qpdf/auto_job_schema.hh b53c006fec2e75b1b73588d242d49a32f7d3db820b1541de106c5d4c27fbb4d9
16 16 manual/_ext/qpdf.py 6add6321666031d55ed4aedf7c00e5662bba856dfcd66ccb526563bffefbb580
17   -manual/cli.rst b524f96f2a6f338f3e4350703598c56ba22e8f12a8efb7a441648c6dbf0a455e
  17 +manual/cli.rst 28cc6b36b26377404022bab467e6a16085023fdfa5d9d419595ffcae6c69d531
... ...
libqpdf/QPDFJob_argv.cc
... ... @@ -35,6 +35,9 @@ namespace
35 35 std::shared_ptr<QPDFJob::EncConfig> c_enc;
36 36 std::vector<std::string> accumulated_args;
37 37 std::shared_ptr<char> pages_password{nullptr};
  38 + std::string user_password;
  39 + std::string owner_password;
  40 + bool used_enc_password_args{false};
38 41 bool gave_input{false};
39 42 bool gave_output{false};
40 43 };
... ... @@ -161,64 +164,67 @@ void
161 164 ArgParser::argEncrypt()
162 165 {
163 166 this->accumulated_args.clear();
164   - if (this->ap.isCompleting() && this->ap.argsLeft() == 0) {
165   - this->ap.insertCompletion("user-password");
166   - }
167 167 this->ap.selectOptionTable(O_ENCRYPTION);
168 168 }
169 169  
170 170 void
171 171 ArgParser::argEncPositional(std::string const& arg)
172 172 {
  173 + if (used_enc_password_args) {
  174 + usage("positional and dashed encryption arguments may not be mixed");
  175 + }
  176 +
173 177 this->accumulated_args.push_back(arg);
174   - size_t n_args = this->accumulated_args.size();
175   - if (n_args < 3) {
176   - if (this->ap.isCompleting() && (this->ap.argsLeft() == 0)) {
177   - if (n_args == 1) {
178   - this->ap.insertCompletion("owner-password");
179   - } else if (n_args == 2) {
180   - this->ap.insertCompletion("40");
181   - this->ap.insertCompletion("128");
182   - this->ap.insertCompletion("256");
183   - }
184   - }
  178 + if (this->accumulated_args.size() < 3) {
185 179 return;
186 180 }
187   - std::string user_password = this->accumulated_args.at(0);
188   - std::string owner_password = this->accumulated_args.at(1);
189   - std::string len_str = this->accumulated_args.at(2);
190   - int keylen = 0;
191   - if (len_str == "40") {
192   - keylen = 40;
193   - this->ap.selectOptionTable(O_40_BIT_ENCRYPTION);
194   - } else if (len_str == "128") {
195   - keylen = 128;
196   - this->ap.selectOptionTable(O_128_BIT_ENCRYPTION);
197   - } else if (len_str == "256") {
198   - keylen = 256;
199   - this->ap.selectOptionTable(O_256_BIT_ENCRYPTION);
200   - } else {
201   - usage("encryption key length must be 40, 128, or 256");
202   - }
203   - this->c_enc = c_main->encrypt(keylen, user_password, owner_password);
  181 + user_password = this->accumulated_args.at(0);
  182 + owner_password = this->accumulated_args.at(1);
  183 + auto len_str = this->accumulated_args.at(2);
  184 + this->accumulated_args.clear();
  185 + argEncBits(len_str);
204 186 }
205 187  
206 188 void
207 189 ArgParser::argEncUserPassword(std::string const& arg)
208 190 {
209   - // QXXXQ
  191 + if (!accumulated_args.empty()) {
  192 + usage("positional and dashed encryption arguments may not be mixed");
  193 + }
  194 + this->used_enc_password_args = true;
  195 + this->user_password = arg;
210 196 }
211 197  
212 198 void
213 199 ArgParser::argEncOwnerPassword(std::string const& arg)
214 200 {
215   - // QXXXQ
  201 + if (!accumulated_args.empty()) {
  202 + usage("positional and dashed encryption arguments may not be mixed");
  203 + }
  204 + this->used_enc_password_args = true;
  205 + this->owner_password = arg;
216 206 }
217 207  
218 208 void
219 209 ArgParser::argEncBits(std::string const& arg)
220 210 {
221   - // QXXXQ
  211 + if (!accumulated_args.empty()) {
  212 + usage("positional and dashed encryption arguments may not be mixed");
  213 + }
  214 + int keylen = 0;
  215 + if (arg == "40") {
  216 + keylen = 40;
  217 + this->ap.selectOptionTable(O_40_BIT_ENCRYPTION);
  218 + } else if (arg == "128") {
  219 + keylen = 128;
  220 + this->ap.selectOptionTable(O_128_BIT_ENCRYPTION);
  221 + } else if (arg == "256") {
  222 + keylen = 256;
  223 + this->ap.selectOptionTable(O_256_BIT_ENCRYPTION);
  224 + } else {
  225 + usage("encryption key length must be 40, 128, or 256");
  226 + }
  227 + this->c_enc = c_main->encrypt(keylen, user_password, owner_password);
222 228 }
223 229  
224 230 void
... ...
libqpdf/qpdf/auto_job_help.hh
... ... @@ -148,29 +148,14 @@ the structure without changing the content.
148 148 )");
149 149 ap.addOptionHelp("--linearize", "transformation", "linearize (web-optimize) output", R"(Create linearized (web-optimized) output files.
150 150 )");
151   -ap.addOptionHelp("--encrypt", "transformation", "start encryption options", R"(--encrypt user-password owner-password key-length [options] --
  151 +ap.addOptionHelp("--encrypt", "transformation", "start encryption options", R"(--encrypt [options] --
152 152  
153 153 Run qpdf --help=encryption for details.
154 154 )");
155   -ap.addOptionHelp("--user-password", "transformation", "specify user password", R"(--user-password=user-password
156   -
157   -Set the user password.
158   -)");
159   -ap.addOptionHelp("--owner-password", "transformation", "specify owner password", R"(--owner-password=owner-password
160   -
161   -Set the owner password.
162   -)");
163   -ap.addOptionHelp("--bits", "transformation", "specify encryption bit depth", R"(--bits={48|128|256}
164   -
165   -Set the encrypt bit depth. Use 256.
166   -)");
167 155 ap.addOptionHelp("--decrypt", "transformation", "remove encryption from input file", R"(Create an unencrypted output file even if the input file was
168 156 encrypted. Normally qpdf preserves whatever encryption was
169 157 present on the input file. This option overrides that behavior.
170 158 )");
171   -}
172   -static void add_help_3(QPDFArgParser& ap)
173   -{
174 159 ap.addOptionHelp("--remove-restrictions", "transformation", "remove security restrictions from input file", R"(Remove restrictions associated with digitally signed PDF files.
175 160 This may be combined with --decrypt to allow free editing of
176 161 previously signed/encrypted files. This option invalidates the
... ... @@ -187,6 +172,9 @@ ap.addOptionHelp(&quot;--encryption-file-password&quot;, &quot;transformation&quot;, &quot;supply passwor
187 172 If the file named in --copy-encryption requires a password, use
188 173 this option to supply the password.
189 174 )");
  175 +}
  176 +static void add_help_3(QPDFArgParser& ap)
  177 +{
190 178 ap.addOptionHelp("--qdf", "transformation", "enable viewing PDF code in a text editor", R"(Create a PDF file suitable for viewing in a text editor and even
191 179 editing. This is for editing the PDF code, not the page contents.
192 180 All streams that can be uncompressed are uncompressed, and
... ... @@ -282,9 +270,6 @@ ap.addOptionHelp(&quot;--ii-min-bytes&quot;, &quot;transformation&quot;, &quot;set minimum size for --ext
282 270 Don't externalize inline images smaller than this size. The
283 271 default is 1,024. Use 0 for no minimum.
284 272 )");
285   -}
286   -static void add_help_4(QPDFArgParser& ap)
287   -{
288 273 ap.addOptionHelp("--min-version", "transformation", "set minimum PDF version", R"(--min-version=version
289 274  
290 275 Force the PDF version of the output to be at least the specified
... ... @@ -312,6 +297,9 @@ resulting set of pages, where :odd starts with the first page and
312 297 :even starts with the second page. These are odd and even pages
313 298 from the resulting set, not based on the original page numbers.
314 299 )");
  300 +}
  301 +static void add_help_4(QPDFArgParser& ap)
  302 +{
315 303 ap.addHelpTopic("modification", "change parts of the PDF", R"(Modification options make systematic changes to certain parts of
316 304 the PDF, causing the PDF to render differently from the original.
317 305 )");
... ... @@ -404,18 +392,33 @@ ap.addOptionHelp(&quot;--keep-inline-images&quot;, &quot;modification&quot;, &quot;exclude inline images
404 392 )");
405 393 ap.addOptionHelp("--remove-page-labels", "modification", "remove explicit page numbers", R"(Exclude page labels (explicit page numbers) from the output file.
406 394 )");
407   -}
408   -static void add_help_5(QPDFArgParser& ap)
409   -{
410 395 ap.addHelpTopic("encryption", "create encrypted files", R"(Create encrypted files. Usage:
411 396  
  397 +--encrypt \
  398 + [--user-password=user-password] \
  399 + [--owner-password=owner-password] \
  400 + --bits=key-length [options] --
  401 +
  402 +OR
  403 +
412 404 --encrypt user-password owner-password key-length [options] --
413 405  
414   -Either or both of user-password and owner-password may be empty
415   -strings, though setting either to the empty string enables the file
416   -to be opened and decrypted without a password. key-length may be
417   -40, 128, or 256. Encryption options are terminated by "--" by
418   -itself.
  406 +The first form, with flags for the passwords and bit length, was
  407 +introduced in qpdf 11.7.0. Only the --bits option is is mandatory.
  408 +This form allows you to use any text as the password. If passwords
  409 +are specified, they must be given before the --bits option.
  410 +
  411 +The second form has been in qpdf since the beginning and wil
  412 +continue to be supported. Either or both of user-password and
  413 +owner-password may be empty strings.
  414 +
  415 +The key-length parameter must be either 40, 128, or 256. The user
  416 +and/or owner password may be omitted. Omitting either pasword
  417 +enables the PDF file to be opened without a password. Specifying
  418 +the same value for the user and owner password and specifying an
  419 +empty owner password are both considered insecure.
  420 +
  421 +Encryption options are terminated by "--" by itself.
419 422  
420 423 40-bit encryption is insecure, as is 128-bit encryption without
421 424 AES. Use 256-bit encryption unless you have a specific reason to
... ... @@ -468,6 +471,22 @@ Values for modify-opt:
468 471 annotate form + commenting and modifying forms
469 472 all allow full document modification
470 473 )");
  474 +ap.addOptionHelp("--user-password", "encryption", "specify user password", R"(--user-password=user-password
  475 +
  476 +Set the user password of the encrypted file.
  477 +)");
  478 +ap.addOptionHelp("--owner-password", "encryption", "specify owner password", R"(--owner-password=owner-password
  479 +
  480 +Set the owner password of the encrypted file.
  481 +)");
  482 +}
  483 +static void add_help_5(QPDFArgParser& ap)
  484 +{
  485 +ap.addOptionHelp("--bits", "encryption", "specify encryption key length", R"(--bits={48|128|256}
  486 +
  487 +Specify the encryption key length. For best security, always use
  488 +a key length of 256.
  489 +)");
471 490 ap.addOptionHelp("--accessibility", "encryption", "restrict document accessibility", R"(--accessibility=[y|n]
472 491  
473 492 This option is ignored except with very old encryption formats.
... ...
manual/cli.rst
... ... @@ -714,7 +714,7 @@ Related Options
714 714 important cross-reference information typically appears at the end
715 715 of the file.
716 716  
717   -.. qpdf:option:: --encrypt user-password owner-password key-length [options] --
  717 +.. qpdf:option:: --encrypt [options] --
718 718  
719 719 .. help: start encryption options
720 720  
... ... @@ -723,32 +723,6 @@ Related Options
723 723 This flag starts encryption options, used to create encrypted
724 724 files. Please see :ref:`encryption-options` for details.
725 725  
726   -.. qpdf:option:: --user-password=user-password
727   -
728   - .. help: specify user password
729   -
730   - Set the user password.
731   -
732   - Set the user password for the encrypted file.
733   -
734   -.. qpdf:option:: --owner-password=owner-password
735   -
736   - .. help: specify owner password
737   -
738   - Set the owner password.
739   -
740   - Set the owner password for the encrypted file.
741   -
742   -.. qpdf:option:: --bits={48|128|256}
743   -
744   - .. help: specify encryption bit depth
745   -
746   - Set the encrypt bit depth. Use 256.
747   -
748   - Set the bit depth for encrypted files. You should always use
749   - ``--bits=256`` unless you have a strong reason to create a file
750   - with weaker encryption.
751   -
752 726 .. qpdf:option:: --decrypt
753 727  
754 728 .. help: remove encryption from input file
... ... @@ -1758,13 +1732,31 @@ Encryption
1758 1732  
1759 1733 Create encrypted files. Usage:
1760 1734  
  1735 + --encrypt \
  1736 + [--user-password=user-password] \
  1737 + [--owner-password=owner-password] \
  1738 + --bits=key-length [options] --
  1739 +
  1740 + OR
  1741 +
1761 1742 --encrypt user-password owner-password key-length [options] --
1762 1743  
1763   - Either or both of user-password and owner-password may be empty
1764   - strings, though setting either to the empty string enables the file
1765   - to be opened and decrypted without a password. key-length may be
1766   - 40, 128, or 256. Encryption options are terminated by "--" by
1767   - itself.
  1744 + The first form, with flags for the passwords and bit length, was
  1745 + introduced in qpdf 11.7.0. Only the --bits option is is mandatory.
  1746 + This form allows you to use any text as the password. If passwords
  1747 + are specified, they must be given before the --bits option.
  1748 +
  1749 + The second form has been in qpdf since the beginning and wil
  1750 + continue to be supported. Either or both of user-password and
  1751 + owner-password may be empty strings.
  1752 +
  1753 + The key-length parameter must be either 40, 128, or 256. The user
  1754 + and/or owner password may be omitted. Omitting either pasword
  1755 + enables the PDF file to be opened without a password. Specifying
  1756 + the same value for the user and owner password and specifying an
  1757 + empty owner password are both considered insecure.
  1758 +
  1759 + Encryption options are terminated by "--" by itself.
1768 1760  
1769 1761 40-bit encryption is insecure, as is 128-bit encryption without
1770 1762 AES. Use 256-bit encryption unless you have a specific reason to
... ... @@ -1823,17 +1815,38 @@ and :qpdf:ref:`--copy-encryption`. For a more in-depth technical
1823 1815 discussion of how PDF encryption works internally, see
1824 1816 :ref:`pdf-encryption`.
1825 1817  
1826   -To create an encrypted file, use
  1818 +To create an encrypted file, use one of
  1819 +
  1820 +::
  1821 +
  1822 + --encrypt \
  1823 + [--user-password=user-password] \
  1824 + [--owner-password=owner-password] \
  1825 + --bits=key-length [options] --
  1826 +
  1827 +OR
1827 1828  
1828 1829 ::
1829 1830  
1830 1831 --encrypt user-password owner-password key-length [options] --
1831 1832  
1832   -Either or both of :samp:`{user-password}` and :samp:`{owner-password}`
1833   -may be empty strings, though setting either to the empty string
1834   -enables the file to be opened and decrypted without a password..
1835   -:samp:`{key-length}` may be ``40``, ``128``, or ``256``. Encryption
1836   -options are terminated by ``--`` by itself.
  1833 +The first form, with flags for the passwords and bit length, was
  1834 +introduced in qpdf 11.7.0. Only the :qpdf:ref:`--bits` option is is
  1835 +mandatory. This form allows you to use any text as the password. If
  1836 +passwords are specified, they must be given before the
  1837 +:qpdf:ref:`--bits` option.
  1838 +
  1839 +The second form has been in qpdf since the beginning and wil
  1840 +continue to be supported. Either or both of user-password and
  1841 +owner-password may be empty strings.
  1842 +
  1843 +The ``key-length`` parameter must be either ``40``, ``128``, or
  1844 +``256``. The user and/or owner password may be omitted. Omitting
  1845 +either pasword enables the PDF file to be opened without a password.
  1846 +Specifying the same value for the user and owner password and
  1847 +specifying an empty owner password are both considered insecure.
  1848 +
  1849 +Encryption options are terminated by ``--`` by itself.
1837 1850  
1838 1851 40-bit encryption is insecure, as is 128-bit encryption without AES.
1839 1852 Use 256-bit encryption unless you have a specific reason to use an
... ... @@ -1971,6 +1984,36 @@ help for each option.
1971 1984 Related Options
1972 1985 ~~~~~~~~~~~~~~~
1973 1986  
  1987 +.. qpdf:option:: --user-password=user-password
  1988 +
  1989 + .. help: specify user password
  1990 +
  1991 + Set the user password of the encrypted file.
  1992 +
  1993 + Set the user passwrod of the encrypted file. Conforming readers
  1994 + apply security restrictions to files opened with the user password.
  1995 +
  1996 +.. qpdf:option:: --owner-password=owner-password
  1997 +
  1998 + .. help: specify owner password
  1999 +
  2000 + Set the owner password of the encrypted file.
  2001 +
  2002 + Set the owner passwrod of the encrypted file. Conforming readers
  2003 + apply allow security restrictions to be changed or overridden when
  2004 + files are opened with the owner password.
  2005 +
  2006 +.. qpdf:option:: --bits={48|128|256}
  2007 +
  2008 + .. help: specify encryption key length
  2009 +
  2010 + Specify the encryption key length. For best security, always use
  2011 + a key length of 256.
  2012 +
  2013 + Set the key length for encrypted files. You should always use
  2014 + ``--bits=256`` unless you have a strong reason to create a file
  2015 + with weaker encryption.
  2016 +
1974 2017 .. qpdf:option:: --accessibility=[y|n]
1975 2018  
1976 2019 .. help: restrict document accessibility
... ...
manual/release-notes.rst
... ... @@ -61,6 +61,16 @@ Planned changes for future 12.x (subject to change):
61 61 Previously, any value other than ``/Yes`` or ``/Off`` was
62 62 rejected.
63 63  
  64 + - CLI Enhancements:
  65 +
  66 + - Allow the syntax ``--encrypt --user-password=user-password
  67 + --owner-password=owner-password --bits={40,128,256}`` when
  68 + encrypting PDF files. This is an alternative to the syntax
  69 + ``--encrypt user-password owner-password {40,128,256}``, which
  70 + will continue to be supported. The new syntax works better with
  71 + shell completion and allows creation of passwords that start
  72 + with ``-``.
  73 +
64 74 - Build Enhancements:
65 75  
66 76 - The qpdf test suite now passes when qpdf is linked with an
... ...
qpdf/qtest/arg-parsing.test
... ... @@ -15,7 +15,7 @@ cleanup();
15 15  
16 16 my $td = new TestDriver('arg-parsing');
17 17  
18   -my $n_tests = 17;
  18 +my $n_tests = 21;
19 19  
20 20 $td->runtest("required argument",
21 21 {$td->COMMAND => "qpdf --password minimal.pdf"},
... ... @@ -108,5 +108,21 @@ $td-&gt;runtest(&quot;empty and replace-input&quot;,
108 108 $td->EXIT_STATUS => 2},
109 109 $td->NORMALIZE_NEWLINES);
110 110  
  111 +# Disallow mixing positional and flag-style encryption arguments.
  112 +my @bad_enc = (
  113 + "u --owner-password=x",
  114 + "u o --bits=128",
  115 + "--user-password=u o",
  116 + "--user-password=u --owner-password=o 256",
  117 + );
  118 +foreach my $arg (@bad_enc)
  119 +{
  120 + $td->runtest("mixed encryption args ($arg)",
  121 + {$td->COMMAND => "qpdf --encrypt $arg"},
  122 + {$td->REGEXP => ".*positional and dashed encryption arguments may not be mixed",
  123 + $td->EXIT_STATUS => 2},
  124 + $td->NORMALIZE_NEWLINES);
  125 +}
  126 +
111 127 cleanup();
112 128 $td->report($n_tests);
... ...
qpdf/qtest/completion.test
... ... @@ -29,14 +29,7 @@ my @completion_tests = (
29 29 ['qpdf ', undef, 'top'],
30 30 ['qpdf -', undef, 'top-arg'],
31 31 ['qpdf --enc', undef, 'enc'],
32   - ['qpdf --encrypt ', undef, 'encrypt'],
33   - ['qpdf --encrypt u ', undef, 'encrypt-u'],
34   - ['qpdf --encrypt u o ', undef, 'encrypt-u-o'],
35   - ['qpdf @encrypt-u o ', undef, 'encrypt-u-o'],
36   - ['qpdf --encrypt u o 40 --', undef, 'encrypt-40'],
37   - ['qpdf --encrypt u o 128 --', undef, 'encrypt-128'],
38   - ['qpdf --encrypt u o 256 --', undef, 'encrypt-256'],
39   - ['qpdf --encrypt u o bad --', undef, 'encrypt-bad'],
  32 + ['qpdf --encrypt -', undef, 'encrypt'],
40 33 ['qpdf --split-pag', undef, 'split'],
41 34 ['qpdf --decode-l', undef, 'decode-l'],
42 35 ['qpdf --decode-lzzz', 15, 'decode-l'],
... ... @@ -44,11 +37,11 @@ my @completion_tests = (
44 37 ['qpdf --decode-level=g', undef, 'decode-level-g'],
45 38 ['qpdf --check -', undef, 'later-arg'],
46 39 ['qpdf infile outfile oops --ch', undef, 'usage-empty'],
47   - ['qpdf --encrypt \'user " password\' ', undef, 'quoting'],
48   - ['qpdf --encrypt \'user password\' ', undef, 'quoting'],
49   - ['qpdf --encrypt "user password" ', undef, 'quoting'],
50   - ['qpdf --encrypt "user pass\'word" ', undef, 'quoting'],
51   - ['qpdf --encrypt user\ password ', undef, 'quoting'],
  40 + ['qpdf \'input " file\' --q', undef, 'quoting'],
  41 + ['qpdf \'input file\' --q', undef, 'quoting'],
  42 + ['qpdf "input file" --q', undef, 'quoting'],
  43 + ['qpdf "input fi\'le" --q', undef, 'quoting'],
  44 + ['qpdf input\ file --q', undef, 'quoting'],
52 45 );
53 46 my $n_tests = 2 * scalar(@completion_tests);
54 47 my $completion_filter =
... ...
qpdf/qtest/qpdf/completion-encrypt-zsh.out 0 → 100644
  1 +--bits=128
  2 +--bits=256
  3 +--bits=40
  4 +--owner-password=
  5 +--user-password=
  6 +!--print
... ...
qpdf/qtest/qpdf/completion-encrypt.out
1   -user-password
  1 +--bits=
  2 +--owner-password=
  3 +--user-password=
  4 +!--bits=128
  5 +!--bits=256
  6 +!--bits=40
2 7 !--print
... ...
qpdf/qtest/qpdf/completion-quoting.out
1   -owner-password
  1 +--qdf
... ...
qpdf/qtest/qpdf/encrypt-u
1 1 --encrypt
2   -u
  2 +--bits=2
... ...