Commit c2030d1f333b9403cfb15460c4b9ca9fcb446021
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.
Showing
21 changed files
with
416 additions
and
2 deletions
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<QPDF> 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& pdf, Options& 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& pdf, Options& o, QPDFWriter& 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 | +Á•Ž€ | ... | ... |