Commit c2030d1f333b9403cfb15460c4b9ca9fcb446021

Authored by Jay Berkenbilt
1 parent 392f2ece

Implement password recovery suppression and password mode (fixes #215)

Allow fine control over how passwords are encoded for writing, and
allow password for reading to be given as a hexademical encoded
string. Allow suppression of password recovery as a means to ensure
that the password you specify is actually the right one.
ChangeLog
  1 +2019-01-19 Jay Berkenbilt <ejb@ql.org>
  2 +
  3 + * NOTE: qpdf CLI: some non-compatible changes were made to how
  4 + qpdf interprets password arguments that contain Unicode characters
  5 + that fall outside of ASCII. On Windows, the non-compatibility was
  6 + unavoidable, as explained in the release notes. On all platforms,
  7 + it is possible to get the old behavior if desired, though the old
  8 + behavior would almost always result in files that other
  9 + applications were unable to open. As it stands, qpdf should now be
  10 + able to open passwords encrypted with a wide range of passwords
  11 + that some other viewers might not handle, though even now, qpdf's
  12 + Unicode password handling is not 100% complete.
  13 +
  14 + * Add --password-mode option, which allows fine-grained control of
  15 + how password arguments are treated. This is discussed fully in the
  16 + manual. Fixes #215.
  17 +
  18 + * Add option --suppress-password-recovery to disable the behavior
  19 + of searching for a correct password by re-encoding the provided
  20 + password. This option can be useful if you want to ensure you know
  21 + exactly what password is being used.
  22 +
1 23 2019-01-17 Jay Berkenbilt <ejb@ql.org>
2 24  
3 25 * When attempting to open an encrypted file with a password, if
... ...
qpdf/qpdf.cc
... ... @@ -58,6 +58,8 @@ struct RotationSpec
58 58 bool relative;
59 59 };
60 60  
  61 +enum password_mode_e { pm_bytes, pm_hex_bytes, pm_unicode, pm_auto };
  62 +
61 63 struct Options
62 64 {
63 65 Options() :
... ... @@ -73,6 +75,8 @@ struct Options
73 75 encryption_file_password(0),
74 76 encrypt(false),
75 77 password_is_hex_key(false),
  78 + suppress_password_recovery(false),
  79 + password_mode(pm_auto),
76 80 keylen(0),
77 81 r2_print(true),
78 82 r2_modify(true),
... ... @@ -154,6 +158,8 @@ struct Options
154 158 char const* encryption_file_password;
155 159 bool encrypt;
156 160 bool password_is_hex_key;
  161 + bool suppress_password_recovery;
  162 + password_mode_e password_mode;
157 163 std::string user_password;
158 164 std::string owner_password;
159 165 int keylen;
... ... @@ -572,6 +578,8 @@ class ArgParser
572 578 void argEncrypt();
573 579 void argDecrypt();
574 580 void argPasswordIsHexKey();
  581 + void argPasswordMode(char* parameter);
  582 + void argSuppressPasswordRecovery();
575 583 void argCopyEncryption(char* parameter);
576 584 void argEncryptionFilePassword(char* parameter);
577 585 void argPages();
... ... @@ -760,6 +768,12 @@ ArgParser::initOptionTable()
760 768 (*t)["encrypt"] = oe_bare(&ArgParser::argEncrypt);
761 769 (*t)["decrypt"] = oe_bare(&ArgParser::argDecrypt);
762 770 (*t)["password-is-hex-key"] = oe_bare(&ArgParser::argPasswordIsHexKey);
  771 + (*t)["suppress-password-recovery"] =
  772 + oe_bare(&ArgParser::argSuppressPasswordRecovery);
  773 + char const* password_mode_choices[] =
  774 + {"bytes", "hex-bytes", "unicode", "auto", 0};
  775 + (*t)["password-mode"] = oe_requiredChoices(
  776 + &ArgParser::argPasswordMode, password_mode_choices);
763 777 (*t)["copy-encryption"] = oe_requiredParameter(
764 778 &ArgParser::argCopyEncryption, "file");
765 779 (*t)["encryption-file-password"] = oe_requiredParameter(
... ... @@ -986,6 +1000,10 @@ ArgParser::argHelp()
986 1000 << "--encrypt options -- generate an encrypted file\n"
987 1001 << "--decrypt remove any encryption on the file\n"
988 1002 << "--password-is-hex-key treat primary password option as a hex-encoded key\n"
  1003 + << "--suppress-password-recovery\n"
  1004 + << " do not attempt recovering from password string\n"
  1005 + << " encoding errors\n"
  1006 + << "--password-mode=mode control qpdf's encoding of passwords\n"
989 1007 << "--pages options -- select specific pages from one or more files\n"
990 1008 << "--collate causes files specified in --pages to be collated\n"
991 1009 << " rather than concatenated\n"
... ... @@ -1097,6 +1115,20 @@ ArgParser::argHelp()
1097 1115 << "for testing qpdf and has no other practical use.\n"
1098 1116 << "\n"
1099 1117 << "\n"
  1118 + << "Password Modes\n"
  1119 + << "----------------------\n"
  1120 + << "\n"
  1121 + << "The --password-mode controls how qpdf interprets passwords supplied\n"
  1122 + << "on the command-line. qpdf's default behavior is correct in almost all\n"
  1123 + << "cases, but you can fine-tune with this option.\n"
  1124 + << "\n"
  1125 + << " bytes: use the password literally as supplied\n"
  1126 + << " hex-bytes: interpret the password as ahex-encoded byte string\n"
  1127 + << " unicode: interpret the password as a UTF-8 encoded string\n"
  1128 + << " auto: attempt to infer the encoding and adjust as needed\n"
  1129 + << "\n"
  1130 + << "This is a complex topic. See the manual for a complete discussion.\n"
  1131 + << "\n"
1100 1132 << "Page Selection Options\n"
1101 1133 << "----------------------\n"
1102 1134 << "\n"
... ... @@ -1434,6 +1466,37 @@ ArgParser::argPasswordIsHexKey()
1434 1466 }
1435 1467  
1436 1468 void
  1469 +ArgParser::argSuppressPasswordRecovery()
  1470 +{
  1471 + o.suppress_password_recovery = true;
  1472 +}
  1473 +
  1474 +void
  1475 +ArgParser::argPasswordMode(char* parameter)
  1476 +{
  1477 + if (strcmp(parameter, "bytes") == 0)
  1478 + {
  1479 + o.password_mode = pm_bytes;
  1480 + }
  1481 + else if (strcmp(parameter, "hex-bytes") == 0)
  1482 + {
  1483 + o.password_mode = pm_hex_bytes;
  1484 + }
  1485 + else if (strcmp(parameter, "unicode") == 0)
  1486 + {
  1487 + o.password_mode = pm_unicode;
  1488 + }
  1489 + else if (strcmp(parameter, "auto") == 0)
  1490 + {
  1491 + o.password_mode = pm_auto;
  1492 + }
  1493 + else
  1494 + {
  1495 + usage("invalid password-mode option");
  1496 + }
  1497 +}
  1498 +
  1499 +void
1437 1500 ArgParser::argCopyEncryption(char* parameter)
1438 1501 {
1439 1502 o.encryption_file = parameter;
... ... @@ -3705,9 +3768,23 @@ static PointerHolder&lt;QPDF&gt; do_process(
3705 3768 // by the password given here was incorrectly encoded, there's a
3706 3769 // good chance we'd succeed here.
3707 3770  
3708   - if ((password == 0) || empty || o.password_is_hex_key)
  3771 + std::string ptemp;
  3772 + if (password && (! o.password_is_hex_key))
  3773 + {
  3774 + if (o.password_mode == pm_hex_bytes)
  3775 + {
  3776 + // Special case: handle --password-mode=hex-bytes for input
  3777 + // password as well as output password
  3778 + QTC::TC("qpdf", "qpdf input password hex-bytes");
  3779 + ptemp = QUtil::hex_decode(password);
  3780 + password = ptemp.c_str();
  3781 + }
  3782 + }
  3783 + if ((password == 0) || empty || o.password_is_hex_key ||
  3784 + o.suppress_password_recovery)
3709 3785 {
3710   - // There is no password, so just do the normal processing.
  3786 + // There is no password, or we're not doing recovery, so just
  3787 + // do the normal processing with the supplied password.
3711 3788 return do_process_once(fn, item, password, o, empty);
3712 3789 }
3713 3790  
... ... @@ -4148,6 +4225,103 @@ static void handle_rotations(QPDF&amp; pdf, Options&amp; o)
4148 4225 }
4149 4226 }
4150 4227  
  4228 +static void maybe_fix_write_password(int R, Options& o, std::string& password)
  4229 +{
  4230 + switch (o.password_mode)
  4231 + {
  4232 + case pm_bytes:
  4233 + QTC::TC("qpdf", "qpdf password mode bytes");
  4234 + break;
  4235 +
  4236 + case pm_hex_bytes:
  4237 + QTC::TC("qpdf", "qpdf password mode hex-bytes");
  4238 + password = QUtil::hex_decode(password);
  4239 + break;
  4240 +
  4241 + case pm_unicode:
  4242 + case pm_auto:
  4243 + {
  4244 + bool has_8bit_chars;
  4245 + bool is_valid_utf8;
  4246 + bool is_utf16;
  4247 + QUtil::analyze_encoding(password,
  4248 + has_8bit_chars,
  4249 + is_valid_utf8,
  4250 + is_utf16);
  4251 + if (! has_8bit_chars)
  4252 + {
  4253 + return;
  4254 + }
  4255 + if (o.password_mode == pm_unicode)
  4256 + {
  4257 + if (! is_valid_utf8)
  4258 + {
  4259 + QTC::TC("qpdf", "qpdf password not unicode");
  4260 + throw std::runtime_error(
  4261 + "supplied password is not valid UTF-8");
  4262 + }
  4263 + if (R < 5)
  4264 + {
  4265 + std::string encoded;
  4266 + if (! QUtil::utf8_to_pdf_doc(password, encoded))
  4267 + {
  4268 + QTC::TC("qpdf", "qpdf password not encodable");
  4269 + throw std::runtime_error(
  4270 + "supplied password cannot be encoded for"
  4271 + " 40-bit or 128-bit encryption formats");
  4272 + }
  4273 + password = encoded;
  4274 + }
  4275 + }
  4276 + else
  4277 + {
  4278 + if ((R < 5) && is_valid_utf8)
  4279 + {
  4280 + std::string encoded;
  4281 + if (QUtil::utf8_to_pdf_doc(password, encoded))
  4282 + {
  4283 + QTC::TC("qpdf", "qpdf auto-encode password");
  4284 + if (o.verbose)
  4285 + {
  4286 + std::cout
  4287 + << whoami
  4288 + << ": automatically converting Unicode"
  4289 + << " password to single-byte encoding as"
  4290 + << " required for 40-bit or 128-bit"
  4291 + << " encryption" << std::endl;
  4292 + }
  4293 + password = encoded;
  4294 + }
  4295 + else
  4296 + {
  4297 + QTC::TC("qpdf", "qpdf bytes fallback warning");
  4298 + std::cerr
  4299 + << whoami << ": WARNING: "
  4300 + << "supplied password looks like a Unicode"
  4301 + << " password with characters not allowed in"
  4302 + << " passwords for 40-bit and 128-bit encryption;"
  4303 + << " most readers will not be able to open this"
  4304 + << " file with the supplied password."
  4305 + << " (Use --password-mode=bytes to suppress this"
  4306 + << " warning and use the password anyway.)"
  4307 + << std::endl;
  4308 + }
  4309 + }
  4310 + else if ((R >= 5) && (! is_valid_utf8))
  4311 + {
  4312 + QTC::TC("qpdf", "qpdf invalid utf-8 in auto");
  4313 + throw std::runtime_error(
  4314 + "supplied password is not a valid Unicode password,"
  4315 + " which is required for 256-bit encryption; to"
  4316 + " really use this password, rerun with the"
  4317 + " --password-mode=bytes option");
  4318 + }
  4319 + }
  4320 + }
  4321 + break;
  4322 + }
  4323 +}
  4324 +
4151 4325 static void set_encryption_options(QPDF& pdf, Options& o, QPDFWriter& w)
4152 4326 {
4153 4327 int R = 0;
... ... @@ -4187,6 +4361,8 @@ static void set_encryption_options(QPDF&amp; pdf, Options&amp; o, QPDFWriter&amp; w)
4187 4361 << ": -accessibility=n is ignored for modern"
4188 4362 << " encryption formats" << std::endl;
4189 4363 }
  4364 + maybe_fix_write_password(R, o, o.user_password);
  4365 + maybe_fix_write_password(R, o, o.owner_password);
4190 4366 switch (R)
4191 4367 {
4192 4368 case 2:
... ...
qpdf/qpdf.testcov
... ... @@ -413,3 +413,11 @@ QPDF copy foreign stream with buffer 0
413 413 QPDF immediate copy stream data 0
414 414 qpdf copy same page more than once 1
415 415 QPDFPageObjectHelper bad token finding names 0
  416 +qpdf password mode bytes 0
  417 +qpdf password mode hex-bytes 0
  418 +qpdf password not unicode 0
  419 +qpdf password not encodable 0
  420 +qpdf auto-encode password 0
  421 +qpdf bytes fallback warning 0
  422 +qpdf invalid utf-8 in auto 0
  423 +qpdf input password hex-bytes 0
... ...
qpdf/qtest/qpdf.test
... ... @@ -3222,6 +3222,188 @@ foreach my $d (@enc_key)
3222 3222  
3223 3223 show_ntests();
3224 3224 # ----------
  3225 +$td->notify("--- Unicode Passwords ---");
  3226 +# $n_tests incremented below
  3227 +
  3228 +# Files with each of these passwords when properly encoded have been
  3229 +# tested manually with multiple PDF viewers. Adobe Reader, chrome,
  3230 +# xpdf, and gv can open all of them except R3 with "single-byte",
  3231 +# which can be opened by xpdf and gv but not the others. As of
  3232 +# 2019-01-19, okular and atril (evince) are not able to open R=6 files
  3233 +# with Unicode passwords as generated by qpdf but can open the R=3
  3234 +# files.
  3235 +
  3236 +# [bits, password-or-password-name, write-encoding, actual-encoding, xargs,
  3237 +# [[read-encoding, strict?, fail?, tried-others, xargs]]]
  3238 +my @unicode_pw_cases = (
  3239 + [128, 'simple', 'pdf-doc', 'pdf-doc', '',
  3240 + [['utf8', 0, 0, 1, ''],
  3241 + ['utf8', 1, 1, 0, ''],
  3242 + ['pdf-doc', 1, 0, 0, ''],
  3243 + ]],
  3244 + [128, 'simple', 'utf8', 'utf8', '--password-mode=bytes',
  3245 + [['pdf-doc', 0, 0, 1, ''],
  3246 + ['pdf-doc', 1, 1, 0, ''],
  3247 + ['utf8', 1, 0, 0, ''],
  3248 + ]],
  3249 + [128, 'simple', 'utf8', 'pdf-doc', '--password-mode=unicode',
  3250 + [['pdf-doc', 1, 0, 0, ''],
  3251 + ]],
  3252 + [128, 'simple', 'utf8', 'pdf-doc', '--password-mode=auto',
  3253 + [['pdf-doc', 1, 0, 0, ''],
  3254 + ]],
  3255 + [128, 'single-byte', 'utf8', 'pdf-doc', '',
  3256 + [['pdf-doc', 1, 0, 0, ''],
  3257 + ['win-ansi', 0, 0, 1, ''],
  3258 + ]],
  3259 + [128, 'single-byte', 'utf8', 'pdf-doc', '--password-mode=unicode',
  3260 + [['pdf-doc', 1, 0, 0, ''],
  3261 + ['win-ansi', 0, 0, 1, ''],
  3262 + ]],
  3263 + [128, 'single-byte', 'win-ansi', '', '--password-mode=unicode',
  3264 + "supplied password is not valid UTF-8\n",
  3265 + ],
  3266 + [128, 'single-byte', 'win-ansi', 'win-ansi', '',
  3267 + [['win-ansi', 1, 0, 0, ''],
  3268 + ]],
  3269 + [128, 'single-byte', 'pdf-doc', 'pdf-doc', '',
  3270 + [['pdf-doc', 1, 0, 0, ''],
  3271 + ['win-ansi', 0, 0, 1, ''],
  3272 + ['pdf-doc-hex', 1, 0, 0, '--password-mode=hex-bytes'],
  3273 + ]],
  3274 + [128, 'complex', 'utf8', '', '--password-mode=unicode',
  3275 + "supplied password cannot be encoded for 40-bit or" .
  3276 + " 128-bit encryption formats\n"
  3277 + ],
  3278 + [128, 'complex', 'utf8', 'utf8', '--password-mode=bytes',
  3279 + [['utf8', 1, 0, 0, ''],
  3280 + ]],
  3281 + [256, 'single-byte', 'win-ansi', '', '--password-mode=unicode',
  3282 + "supplied password is not valid UTF-8\n",
  3283 + ],
  3284 + [256, 'single-byte', 'win-ansi', '', '--password-mode=auto',
  3285 + "supplied password is not a valid Unicode password, which is" .
  3286 + " required for 256-bit encryption; to really use this password," .
  3287 + " rerun with the --password-mode=bytes option\n",
  3288 + ],
  3289 + [256, 'single-byte', 'win-ansi', 'win-ansi', '--password-mode=bytes',
  3290 + [['utf8', 0, 0, 1, ''],
  3291 + ['utf8', 1, 1, 0, ''],
  3292 + ['win-ansi', 1, 0, 0, ''],
  3293 + ['win-ansi', 0, 0, 0, ''],
  3294 + ['pdf-doc', 0, 0, 1, ''],
  3295 + ['pdf-doc-hex', 0, 0, 1, '--password-mode=hex-bytes'],
  3296 + ]],
  3297 + [256, 'complex', 'utf8', 'utf8', '',
  3298 + [['utf8', 1, 0, 0, ''],
  3299 + ['utf8-hex', 1, 0, 0, '--password-mode=hex-bytes'],
  3300 + ]],
  3301 + [256, 'complex', 'utf8-hex', 'utf8', '--password-mode=hex-bytes',
  3302 + [['utf8', 1, 0, 0, ''],
  3303 + ['utf8-hex', 1, 0, 0, '--password-mode=hex-bytes'],
  3304 + ]],
  3305 + [256, 'complex', 'utf8', 'utf8', '--password-mode=unicode',
  3306 + [['utf8', 1, 0, 0, ''],
  3307 + ['password-arg-simple-utf8', 0, 1, 1, ''],
  3308 + ]],
  3309 + );
  3310 +
  3311 +for my $d (@unicode_pw_cases)
  3312 +{
  3313 + my $decode_cases = $d->[5];
  3314 + $n_tests += 1;
  3315 + if (ref($decode_cases) eq 'ARRAY')
  3316 + {
  3317 + $n_tests += scalar(@$decode_cases);
  3318 + }
  3319 +}
  3320 +
  3321 +foreach my $d (@unicode_pw_cases)
  3322 +{
  3323 + my ($bits, $pw, $w_encoding, $a_encoding, $xargs, $decode_cases) = @$d;
  3324 + my $w_pfile = "password-bare-$pw-$w_encoding";
  3325 + my $upass;
  3326 + if (-f $w_pfile)
  3327 + {
  3328 + $upass = '@' . $w_pfile;
  3329 + }
  3330 + else
  3331 + {
  3332 + $upass = "$pw";
  3333 + }
  3334 + my $outbase = "unicode-pw-$bits-$pw-$w_encoding-$xargs";
  3335 + my $exp = '';
  3336 + if (ref($decode_cases) ne 'ARRAY')
  3337 + {
  3338 + $exp = $decode_cases;
  3339 + $decode_cases = [];
  3340 + }
  3341 + $td->runtest("encode $bits, $pw, $w_encoding",
  3342 + {$td->COMMAND =>
  3343 + "qpdf $xargs --static-id --static-aes-iv" .
  3344 + " --encrypt $upass o $bits -- minimal.pdf a.pdf"},
  3345 + {$td->STRING => $exp, $td->EXIT_STATUS => ($exp ? 2 : 0)},
  3346 + $td->NORMALIZE_NEWLINES);
  3347 + foreach my $d2 (@$decode_cases)
  3348 + {
  3349 + my ($r_encoding, $strict, $xfail, $tried_others, $r_xargs) = @$d2;
  3350 + my $r_pfile = "password-arg-$pw-$r_encoding";
  3351 + if (! -f $r_pfile)
  3352 + {
  3353 + $r_pfile = $r_encoding;
  3354 + }
  3355 + my $r_output = "";
  3356 + $r_output .= "trying other\n" if $tried_others;
  3357 + if ($xfail)
  3358 + {
  3359 + $r_output .= "a.pdf: invalid password\n";
  3360 + }
  3361 + else
  3362 + {
  3363 + $r_output .= "R = " . ($bits == 128 ? '3' : '6') . "\n";
  3364 + open(F, "<password-bare-$pw-$a_encoding") or die;
  3365 + chomp (my $apw = <F>);
  3366 + close(F);
  3367 + $r_output .= "User password = $apw\n";
  3368 + }
  3369 + $r_xargs .= $strict ? ' --suppress-password-recovery' : '';
  3370 + $td->runtest("decrypt $pw, $r_encoding, strict=$strict",
  3371 + {$td->COMMAND =>
  3372 + "qpdf --show-encryption --verbose" .
  3373 + " $r_xargs a.pdf \@$r_pfile",
  3374 + $td->FILTER => "perl show-unicode-encryption.pl"},
  3375 + {$td->STRING => "$r_output",
  3376 + $td->EXIT_STATUS => ($xfail ? 2 : 0)},
  3377 + $td->NORMALIZE_NEWLINES);
  3378 + }
  3379 +}
  3380 +
  3381 +$n_tests += 2;
  3382 +$td->runtest("bytes fallback warning",
  3383 + {$td->COMMAND =>
  3384 + "qpdf --encrypt \@password-bare-complex-utf8 o 128 --" .
  3385 + " minimal.pdf a.pdf"},
  3386 + {$td->FILE => "bytes-fallback.out", $td->EXIT_STATUS => 0},
  3387 + $td->NORMALIZE_NEWLINES);
  3388 +{ # local scope
  3389 + my $r_output = "";
  3390 + $r_output .= "R = 3\n";
  3391 + open(F, "<password-bare-complex-utf8") or die;
  3392 + chomp (my $apw = <F>);
  3393 + close(F);
  3394 + $r_output .= "User password = $apw\n";
  3395 + $td->runtest("decrypt bytes fallback",
  3396 + {$td->COMMAND =>
  3397 + "qpdf --show-encryption --verbose" .
  3398 + " a.pdf \@password-arg-complex-utf8" .
  3399 + " --password-mode=bytes",
  3400 + $td->FILTER => "perl show-unicode-encryption.pl"},
  3401 + {$td->STRING => "$r_output", $td->EXIT_STATUS => 0},
  3402 + $td->NORMALIZE_NEWLINES);
  3403 +}
  3404 +
  3405 +show_ntests();
  3406 +# ----------
3225 3407 $td->notify("--- Check from C API ---");
3226 3408 my @c_check_types = qw(warn clear);
3227 3409 $n_tests += scalar(@c_check_types);
... ...
qpdf/qtest/qpdf/bytes-fallback.out 0 → 100644
  1 +qpdf: WARNING: supplied password looks like a Unicode password with characters not allowed in passwords for 40-bit and 128-bit encryption; most readers will not be able to open this file with the supplied password. (Use --password-mode=bytes to suppress this warning and use the password anyway.)
... ...
qpdf/qtest/qpdf/password-arg-complex-utf8 0 → 100644
  1 +--password=Á•Ž€π🥔
... ...
qpdf/qtest/qpdf/password-arg-complex-utf8-hex 0 → 100644
  1 +--password=c381e280a2c5bde282accf80f09fa594
... ...
qpdf/qtest/qpdf/password-arg-simple-pdf-doc 0 → 100644
  1 +--password=khoai tây
... ...
qpdf/qtest/qpdf/password-arg-simple-utf8 0 → 100644
  1 +--password=khoai tây
... ...
qpdf/qtest/qpdf/password-arg-single-byte-pdf-doc 0 → 100644
  1 +--password=Á€™ 
... ...
qpdf/qtest/qpdf/password-arg-single-byte-pdf-doc-hex 0 → 100644
  1 +--password=c18099a0
... ...
qpdf/qtest/qpdf/password-arg-single-byte-utf8 0 → 100644
  1 +--password=Á•Ž€
... ...
qpdf/qtest/qpdf/password-arg-single-byte-win-ansi 0 → 100644
  1 +--password=Á•Ž€
... ...
qpdf/qtest/qpdf/password-bare-complex-utf8 0 → 100644
  1 +Á•Ž€π🥔
... ...
qpdf/qtest/qpdf/password-bare-complex-utf8-hex 0 → 100644
  1 +c381e280a2c5bde282accf80f09fa594
... ...
qpdf/qtest/qpdf/password-bare-simple-pdf-doc 0 → 100644
  1 +khoai tây
... ...
qpdf/qtest/qpdf/password-bare-simple-utf8 0 → 100644
  1 +khoai tây
... ...
qpdf/qtest/qpdf/password-bare-single-byte-pdf-doc 0 → 100644
  1 +Á€™ 
... ...
qpdf/qtest/qpdf/password-bare-single-byte-utf8 0 → 100644
  1 +Á•Ž€
... ...
qpdf/qtest/qpdf/password-bare-single-byte-win-ansi 0 → 100644
  1 +Á•Ž€
... ...
qpdf/qtest/qpdf/show-unicode-encryption.pl 0 → 100644
  1 +use warnings;
  2 +use strict;
  3 +
  4 +while (<>)
  5 +{
  6 + print if m/invalid password/;
  7 + print "trying other\n" if m/supplied password didn't work/;
  8 + print if m/^R =/;
  9 + print if m/^User password =/;
  10 +}
... ...