use super::objects::PdfDictionary;
use super::{ParseError, ParseResult};
use crate::encryption::{
EncryptionKey, Permissions, Rc4, Rc4Key, StandardSecurityHandler, UserPassword,
};
use crate::objects::ObjectId;
#[derive(Debug, Clone)]
pub struct EncryptionInfo {
pub filter: String,
pub v: i32,
pub r: i32,
pub o: Vec<u8>,
pub u: Vec<u8>,
pub p: i32,
pub length: Option<i32>,
pub ue: Option<Vec<u8>>,
pub oe: Option<Vec<u8>>,
}
pub struct EncryptionHandler {
encryption_info: EncryptionInfo,
security_handler: StandardSecurityHandler,
encryption_key: Option<EncryptionKey>,
file_id: Option<Vec<u8>>,
}
impl EncryptionHandler {
pub fn new(encrypt_dict: &PdfDictionary, file_id: Option<Vec<u8>>) -> ParseResult<Self> {
let encryption_info = Self::parse_encryption_dict(encrypt_dict)?;
let security_handler = match encryption_info.r {
2 => StandardSecurityHandler::rc4_40bit(),
3 | 4 => StandardSecurityHandler::rc4_128bit(),
5 => StandardSecurityHandler::aes_256_r5(),
6 => StandardSecurityHandler::aes_256_r6(),
_ => {
return Err(ParseError::SyntaxError {
position: 0,
message: format!("Encryption revision {} not supported", encryption_info.r),
});
}
};
Ok(Self {
encryption_info,
security_handler,
encryption_key: None,
file_id,
})
}
fn parse_encryption_dict(dict: &PdfDictionary) -> ParseResult<EncryptionInfo> {
let filter = dict
.get("Filter")
.and_then(|obj| obj.as_name())
.map(|name| name.0.as_str())
.ok_or_else(|| ParseError::MissingKey("Filter".to_string()))?;
if filter != "Standard" {
return Err(ParseError::SyntaxError {
position: 0,
message: format!("Encryption filter '{filter}' not supported"),
});
}
let v = dict
.get("V")
.and_then(|obj| obj.as_integer())
.map(|i| i as i32)
.unwrap_or(0);
let r = dict
.get("R")
.and_then(|obj| obj.as_integer())
.map(|i| i as i32)
.ok_or_else(|| ParseError::MissingKey("R".to_string()))?;
let o = dict
.get("O")
.and_then(|obj| obj.as_string())
.ok_or_else(|| ParseError::MissingKey("O".to_string()))?
.as_bytes()
.to_vec();
let u = dict
.get("U")
.and_then(|obj| obj.as_string())
.ok_or_else(|| ParseError::MissingKey("U".to_string()))?
.as_bytes()
.to_vec();
let p = dict
.get("P")
.and_then(|obj| obj.as_integer())
.map(|i| i as i32)
.ok_or_else(|| ParseError::MissingKey("P".to_string()))?;
let length = dict
.get("Length")
.and_then(|obj| obj.as_integer())
.map(|i| i as i32);
let ue = dict
.get("UE")
.and_then(|obj| obj.as_string())
.map(|s| s.as_bytes().to_vec());
let oe = dict
.get("OE")
.and_then(|obj| obj.as_string())
.map(|s| s.as_bytes().to_vec());
Ok(EncryptionInfo {
filter: filter.to_string(),
v,
r,
o,
u,
p,
length,
ue,
oe,
})
}
pub fn detect_encryption(trailer: &PdfDictionary) -> bool {
trailer.contains_key("Encrypt")
}
pub fn unlock_with_user_password(&mut self, password: &str) -> ParseResult<bool> {
let user_password = UserPassword(password.to_string());
match self.encryption_info.r {
5 | 6 => self.unlock_user_r5_r6(&user_password),
_ => self.unlock_user_r2_r4(&user_password),
}
}
fn unlock_user_r2_r4(&mut self, user_password: &UserPassword) -> ParseResult<bool> {
let permissions = Permissions::from_bits(self.encryption_info.p as u32);
let computed_u = self
.security_handler
.compute_user_hash(
user_password,
&self.encryption_info.o,
permissions,
self.file_id.as_deref(),
)
.map_err(|e| ParseError::SyntaxError {
position: 0,
message: format!("Failed to compute user hash: {e}"),
})?;
let comparison_length = if self.encryption_info.r >= 3 { 16 } else { 32 };
if computed_u.len() < comparison_length || self.encryption_info.u.len() < comparison_length
{
return Ok(false);
}
let matches =
computed_u[..comparison_length] == self.encryption_info.u[..comparison_length];
if matches {
let key = self
.security_handler
.compute_encryption_key(
user_password,
&self.encryption_info.o,
permissions,
self.file_id.as_deref(),
)
.map_err(|e| ParseError::SyntaxError {
position: 0,
message: format!("Failed to compute encryption key: {e}"),
})?;
self.encryption_key = Some(key);
}
Ok(matches)
}
fn unlock_user_r5_r6(&mut self, user_password: &UserPassword) -> ParseResult<bool> {
let u_entry = &self.encryption_info.u;
let is_valid = if self.encryption_info.r == 5 {
self.security_handler
.validate_r5_user_password(user_password, u_entry)
} else {
self.security_handler
.validate_r6_user_password(user_password, u_entry)
}
.map_err(|e| ParseError::SyntaxError {
position: 0,
message: format!(
"Failed to validate R{} user password: {e}",
self.encryption_info.r
),
})?;
if is_valid {
let ue_entry =
self.encryption_info
.ue
.as_deref()
.ok_or_else(|| ParseError::SyntaxError {
position: 0,
message: format!(
"R{} encryption requires UE entry but it is missing",
self.encryption_info.r
),
})?;
let key = if self.encryption_info.r == 5 {
self.security_handler
.recover_r5_encryption_key(user_password, u_entry, ue_entry)
} else {
self.security_handler
.recover_r6_encryption_key(user_password, u_entry, ue_entry)
}
.map_err(|e| ParseError::SyntaxError {
position: 0,
message: format!(
"Failed to recover R{} encryption key: {e}",
self.encryption_info.r
),
})?;
self.encryption_key = Some(key);
}
Ok(is_valid)
}
pub fn unlock_with_owner_password(&mut self, password: &str) -> ParseResult<bool> {
const PADDING: [u8; 32] = [
0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41, 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA,
0x01, 0x08, 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80, 0x2F, 0x0C, 0xA9, 0xFE,
0x64, 0x53, 0x69, 0x7A,
];
let mut padded = [0u8; 32];
let password_bytes = password.as_bytes();
let len = password_bytes.len().min(32);
padded[..len].copy_from_slice(&password_bytes[..len]);
if len < 32 {
padded[len..].copy_from_slice(&PADDING[..32 - len]);
}
let mut hash = md5::compute(&padded).to_vec();
let key_length = self.security_handler.key_length;
if self.encryption_info.r >= 3 {
for _ in 0..50 {
hash = md5::compute(&hash).to_vec();
}
}
let mut decrypted = self.encryption_info.o[..32].to_vec();
if self.encryption_info.r >= 3 {
for i in (0..20).rev() {
let mut key_bytes = hash[..key_length].to_vec();
for byte in &mut key_bytes {
*byte ^= i as u8;
}
let rc4_key = Rc4Key::from_slice(&key_bytes);
let mut cipher = Rc4::new(&rc4_key);
decrypted = cipher.process(&decrypted);
}
} else {
let rc4_key = Rc4Key::from_slice(&hash[..key_length]);
let mut cipher = Rc4::new(&rc4_key);
decrypted = cipher.process(&decrypted);
}
let user_pwd_end = decrypted
.iter()
.position(|&b| {
b == PADDING[0] && decrypted.len() > 1
})
.unwrap_or(32);
let user_password = String::from_utf8_lossy(&decrypted[..user_pwd_end]).to_string();
#[cfg(debug_assertions)]
{
eprintln!(
"[DEBUG owner unlock] derived user password: {:?}",
&user_password
);
eprintln!(
"[DEBUG owner unlock] decrypted bytes: {:02x?}",
&decrypted[..8]
);
}
if self.unlock_with_user_password(&user_password)? {
return Ok(true);
}
let full_user_password = String::from_utf8_lossy(&decrypted).to_string();
self.unlock_with_user_password(&full_user_password)
}
pub fn try_empty_password(&mut self) -> ParseResult<bool> {
self.unlock_with_user_password("")
}
pub fn is_unlocked(&self) -> bool {
self.encryption_key.is_some()
}
pub fn encryption_key(&self) -> Option<&EncryptionKey> {
self.encryption_key.as_ref()
}
pub fn decrypt_string(&self, data: &[u8], obj_id: &ObjectId) -> ParseResult<Vec<u8>> {
match &self.encryption_key {
Some(key) => Ok(self.security_handler.decrypt_string(data, key, obj_id)),
None => Err(ParseError::EncryptionNotSupported),
}
}
pub fn decrypt_stream(&self, data: &[u8], obj_id: &ObjectId) -> ParseResult<Vec<u8>> {
match &self.encryption_key {
Some(key) => Ok(self.security_handler.decrypt_stream(data, key, obj_id)),
None => Err(ParseError::EncryptionNotSupported),
}
}
pub fn algorithm_info(&self) -> String {
match (
self.encryption_info.r,
self.encryption_info.length.unwrap_or(40),
) {
(2, _) => "RC4 40-bit".to_string(),
(3, len) => format!("RC4 {len}-bit"),
(4, len) => format!("RC4 {len}-bit with metadata control"),
(5, _) => "AES-256 (Revision 5)".to_string(),
(6, _) => "AES-256 (Revision 6, Unicode passwords)".to_string(),
(r, len) => format!("Unknown revision {r} with {len}-bit key"),
}
}
pub fn permissions(&self) -> Permissions {
Permissions::from_bits(self.encryption_info.p as u32)
}
pub fn has_file_id(&self) -> bool {
self.file_id.is_some()
}
pub fn revision(&self) -> i32 {
self.encryption_info.r
}
pub fn encrypt_strings(&self) -> bool {
true
}
pub fn encrypt_streams(&self) -> bool {
true
}
pub fn encrypt_metadata(&self) -> bool {
true
}
}
#[derive(Debug)]
pub enum PasswordResult {
Success,
Rejected,
Cancelled,
}
pub trait PasswordProvider {
fn prompt_user_password(&self) -> ParseResult<Option<String>>;
fn prompt_owner_password(&self) -> ParseResult<Option<String>>;
}
pub struct ConsolePasswordProvider;
impl PasswordProvider for ConsolePasswordProvider {
fn prompt_user_password(&self) -> ParseResult<Option<String>> {
tracing::debug!(
"PDF is encrypted. Enter user password (or press Enter for empty password):"
);
let mut input = String::new();
std::io::stdin()
.read_line(&mut input)
.map_err(|e| ParseError::SyntaxError {
position: 0,
message: format!("Failed to read password: {e}"),
})?;
input.truncate(input.trim_end().len());
Ok(Some(input))
}
fn prompt_owner_password(&self) -> ParseResult<Option<String>> {
tracing::debug!("User password failed. Enter owner password:");
let mut input = String::new();
std::io::stdin()
.read_line(&mut input)
.map_err(|e| ParseError::SyntaxError {
position: 0,
message: format!("Failed to read password: {e}"),
})?;
input.truncate(input.trim_end().len());
Ok(Some(input))
}
}
pub struct InteractiveDecryption<P: PasswordProvider> {
password_provider: P,
}
impl<P: PasswordProvider> InteractiveDecryption<P> {
pub fn new(password_provider: P) -> Self {
Self { password_provider }
}
pub fn unlock_pdf(&self, handler: &mut EncryptionHandler) -> ParseResult<PasswordResult> {
if handler.try_empty_password()? {
return Ok(PasswordResult::Success);
}
if let Some(password) = self.password_provider.prompt_user_password()? {
if handler.unlock_with_user_password(&password)? {
return Ok(PasswordResult::Success);
}
} else {
return Ok(PasswordResult::Cancelled);
}
if let Some(password) = self.password_provider.prompt_owner_password()? {
if handler.unlock_with_owner_password(&password)? {
return Ok(PasswordResult::Success);
}
} else {
return Ok(PasswordResult::Cancelled);
}
Ok(PasswordResult::Rejected)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser::objects::{PdfDictionary, PdfName, PdfObject, PdfString};
fn create_test_encryption_dict() -> PdfDictionary {
let mut dict = PdfDictionary::new();
dict.insert(
"Filter".to_string(),
PdfObject::Name(PdfName("Standard".to_string())),
);
dict.insert("V".to_string(), PdfObject::Integer(1));
dict.insert("R".to_string(), PdfObject::Integer(2));
dict.insert(
"O".to_string(),
PdfObject::String(PdfString::new(vec![0u8; 32])),
);
dict.insert(
"U".to_string(),
PdfObject::String(PdfString::new(vec![0u8; 32])),
);
dict.insert("P".to_string(), PdfObject::Integer(-4));
dict
}
#[test]
fn test_encryption_detection() {
let mut trailer = PdfDictionary::new();
assert!(!EncryptionHandler::detect_encryption(&trailer));
trailer.insert("Encrypt".to_string(), PdfObject::Reference(1, 0));
assert!(EncryptionHandler::detect_encryption(&trailer));
}
#[test]
fn test_encryption_info_parsing() {
let dict = create_test_encryption_dict();
let info = EncryptionHandler::parse_encryption_dict(&dict).unwrap();
assert_eq!(info.filter, "Standard");
assert_eq!(info.v, 1);
assert_eq!(info.r, 2);
assert_eq!(info.o.len(), 32);
assert_eq!(info.u.len(), 32);
assert_eq!(info.p, -4);
}
#[test]
fn test_encryption_handler_creation() {
let dict = create_test_encryption_dict();
let handler = EncryptionHandler::new(&dict, None).unwrap();
assert_eq!(handler.encryption_info.r, 2);
assert!(!handler.is_unlocked());
assert_eq!(handler.algorithm_info(), "RC4 40-bit");
}
#[test]
fn test_empty_password_attempt() {
let dict = create_test_encryption_dict();
let mut handler = EncryptionHandler::new(&dict, None).unwrap();
let result = handler.try_empty_password().unwrap();
assert!(!result);
assert!(!handler.is_unlocked());
}
#[test]
fn test_permissions() {
let dict = create_test_encryption_dict();
let handler = EncryptionHandler::new(&dict, None).unwrap();
let permissions = handler.permissions();
assert!(permissions.bits() != 0);
}
#[test]
fn test_encryption_flags() {
let dict = create_test_encryption_dict();
let handler = EncryptionHandler::new(&dict, None).unwrap();
assert!(handler.encrypt_strings());
assert!(handler.encrypt_streams());
assert!(handler.encrypt_metadata());
}
#[test]
fn test_decrypt_without_key() {
let dict = create_test_encryption_dict();
let handler = EncryptionHandler::new(&dict, None).unwrap();
let obj_id = ObjectId::new(1, 0);
let result = handler.decrypt_string(b"test", &obj_id);
assert!(result.is_err());
}
#[test]
fn test_unsupported_filter() {
let mut dict = PdfDictionary::new();
dict.insert(
"Filter".to_string(),
PdfObject::Name(PdfName("UnsupportedFilter".to_string())),
);
dict.insert("R".to_string(), PdfObject::Integer(2));
dict.insert(
"O".to_string(),
PdfObject::String(PdfString::new(vec![0u8; 32])),
);
dict.insert(
"U".to_string(),
PdfObject::String(PdfString::new(vec![0u8; 32])),
);
dict.insert("P".to_string(), PdfObject::Integer(-4));
let result = EncryptionHandler::new(&dict, None);
assert!(result.is_err());
}
#[test]
fn test_unsupported_revision() {
let mut dict = create_test_encryption_dict();
dict.insert("R".to_string(), PdfObject::Integer(99));
let result = EncryptionHandler::new(&dict, None);
assert!(result.is_err());
}
#[test]
fn test_missing_required_keys() {
let test_cases = vec![
("Filter", PdfObject::Name(PdfName("Standard".to_string()))),
("R", PdfObject::Integer(2)),
("O", PdfObject::String(PdfString::new(vec![0u8; 32]))),
("U", PdfObject::String(PdfString::new(vec![0u8; 32]))),
("P", PdfObject::Integer(-4)),
];
for (skip_key, _) in test_cases {
let mut dict = create_test_encryption_dict();
dict.0.remove(&PdfName(skip_key.to_string()));
let result = EncryptionHandler::parse_encryption_dict(&dict);
assert!(result.is_err(), "Should fail when {skip_key} is missing");
}
}
struct MockPasswordProvider {
user_password: Option<String>,
owner_password: Option<String>,
}
impl PasswordProvider for MockPasswordProvider {
fn prompt_user_password(&self) -> ParseResult<Option<String>> {
Ok(self.user_password.clone())
}
fn prompt_owner_password(&self) -> ParseResult<Option<String>> {
Ok(self.owner_password.clone())
}
}
#[test]
fn test_interactive_decryption_cancelled() {
let provider = MockPasswordProvider {
user_password: None,
owner_password: None,
};
let decryption = InteractiveDecryption::new(provider);
let dict = create_test_encryption_dict();
let mut handler = EncryptionHandler::new(&dict, None).unwrap();
let result = decryption.unlock_pdf(&mut handler).unwrap();
matches!(result, PasswordResult::Cancelled);
}
#[test]
fn test_interactive_decryption_rejected() {
let provider = MockPasswordProvider {
user_password: Some("wrong_password".to_string()),
owner_password: Some("also_wrong".to_string()),
};
let decryption = InteractiveDecryption::new(provider);
let dict = create_test_encryption_dict();
let mut handler = EncryptionHandler::new(&dict, None).unwrap();
let result = decryption.unlock_pdf(&mut handler).unwrap();
matches!(result, PasswordResult::Rejected);
}
#[test]
fn test_malformed_encryption_dictionary_invalid_types() {
let mut dict = PdfDictionary::new();
dict.insert("Filter".to_string(), PdfObject::Integer(123)); dict.insert("R".to_string(), PdfObject::Integer(2));
dict.insert(
"O".to_string(),
PdfObject::String(PdfString::new(vec![0u8; 32])),
);
dict.insert(
"U".to_string(),
PdfObject::String(PdfString::new(vec![0u8; 32])),
);
dict.insert("P".to_string(), PdfObject::Integer(-4));
let result = EncryptionHandler::parse_encryption_dict(&dict);
assert!(result.is_err());
let mut dict = create_test_encryption_dict();
dict.insert(
"R".to_string(),
PdfObject::Name(PdfName("not_a_number".to_string())),
);
let result = EncryptionHandler::parse_encryption_dict(&dict);
assert!(result.is_err());
}
#[test]
fn test_encryption_dictionary_edge_values() {
let mut dict = create_test_encryption_dict();
dict.insert("R".to_string(), PdfObject::Integer(0)); let result = EncryptionHandler::new(&dict, None);
assert!(result.is_err());
let mut dict = create_test_encryption_dict();
dict.insert("R".to_string(), PdfObject::Integer(-1));
let result = EncryptionHandler::new(&dict, None);
assert!(result.is_err());
let mut dict = create_test_encryption_dict();
dict.insert("R".to_string(), PdfObject::Integer(1000));
let result = EncryptionHandler::new(&dict, None);
assert!(result.is_err());
}
#[test]
fn test_encryption_dictionary_invalid_hash_lengths() {
let mut dict = create_test_encryption_dict();
dict.insert(
"O".to_string(),
PdfObject::String(PdfString::new(vec![0u8; 16])),
); let result = EncryptionHandler::parse_encryption_dict(&dict);
assert!(result.is_ok());
let mut dict = create_test_encryption_dict();
dict.insert(
"U".to_string(),
PdfObject::String(PdfString::new(vec![0u8; 64])),
); let result = EncryptionHandler::parse_encryption_dict(&dict);
assert!(result.is_ok());
let mut dict = create_test_encryption_dict();
dict.insert("O".to_string(), PdfObject::String(PdfString::new(vec![])));
dict.insert("U".to_string(), PdfObject::String(PdfString::new(vec![])));
let result = EncryptionHandler::parse_encryption_dict(&dict);
assert!(result.is_ok());
}
#[test]
fn test_encryption_with_different_key_lengths() {
let mut dict = create_test_encryption_dict();
dict.insert("R".to_string(), PdfObject::Integer(2));
let handler = EncryptionHandler::new(&dict, None).unwrap();
assert_eq!(handler.algorithm_info(), "RC4 40-bit");
let mut dict = create_test_encryption_dict();
dict.insert("R".to_string(), PdfObject::Integer(3));
dict.insert("Length".to_string(), PdfObject::Integer(128));
let handler = EncryptionHandler::new(&dict, None).unwrap();
assert_eq!(handler.algorithm_info(), "RC4 128-bit");
let mut dict = create_test_encryption_dict();
dict.insert("R".to_string(), PdfObject::Integer(4));
dict.insert("Length".to_string(), PdfObject::Integer(128));
let handler = EncryptionHandler::new(&dict, None).unwrap();
assert_eq!(
handler.algorithm_info(),
"RC4 128-bit with metadata control"
);
let mut dict = create_test_encryption_dict();
dict.insert("R".to_string(), PdfObject::Integer(5));
dict.insert("V".to_string(), PdfObject::Integer(5));
let handler = EncryptionHandler::new(&dict, None).unwrap();
assert_eq!(handler.algorithm_info(), "AES-256 (Revision 5)");
let mut dict = create_test_encryption_dict();
dict.insert("R".to_string(), PdfObject::Integer(6));
dict.insert("V".to_string(), PdfObject::Integer(5));
let handler = EncryptionHandler::new(&dict, None).unwrap();
assert_eq!(
handler.algorithm_info(),
"AES-256 (Revision 6, Unicode passwords)"
);
}
#[test]
fn test_file_id_handling() {
let dict = create_test_encryption_dict();
let file_id = Some(b"test_file_id_12345678".to_vec());
let handler = EncryptionHandler::new(&dict, file_id.clone()).unwrap();
assert_eq!(handler.file_id, file_id);
let handler = EncryptionHandler::new(&dict, None).unwrap();
assert_eq!(handler.file_id, None);
let empty_file_id = Some(vec![]);
let handler = EncryptionHandler::new(&dict, empty_file_id.clone()).unwrap();
assert_eq!(handler.file_id, empty_file_id);
}
#[test]
fn test_permissions_edge_cases() {
let permission_values = vec![0, -1, -4, -44, -100, i32::MAX, i32::MIN];
for p_value in permission_values {
let mut dict = create_test_encryption_dict();
dict.insert("P".to_string(), PdfObject::Integer(p_value as i64));
let handler = EncryptionHandler::new(&dict, None).unwrap();
let permissions = handler.permissions();
assert_eq!(permissions.bits(), p_value as u32);
}
}
#[test]
fn test_decrypt_with_different_object_ids() {
let dict = create_test_encryption_dict();
let handler = EncryptionHandler::new(&dict, None).unwrap();
let test_data = b"test data";
let object_ids = vec![
ObjectId::new(1, 0),
ObjectId::new(999, 0),
ObjectId::new(1, 999),
ObjectId::new(u32::MAX, u16::MAX),
];
for obj_id in object_ids {
let result = handler.decrypt_string(test_data, &obj_id);
assert!(result.is_err());
let result = handler.decrypt_stream(test_data, &obj_id);
assert!(result.is_err());
}
}
#[test]
fn test_password_scenarios_comprehensive() {
let dict = create_test_encryption_dict();
let mut handler = EncryptionHandler::new(&dict, None).unwrap();
let test_passwords = vec![
"".to_string(), " ".to_string(), " ".to_string(), "password".to_string(), "Password123!@#".to_string(), "a".repeat(32), "a".repeat(50), "unicode_ñáéíóú".to_string(), "pass\nwith\nnewlines".to_string(), "pass\twith\ttabs".to_string(), "pass with spaces".to_string(), "🔐🗝️📄".to_string(), ];
for password in test_passwords {
let result = handler.unlock_with_user_password(&password);
assert!(result.is_ok());
assert!(!result.unwrap());
let result = handler.unlock_with_owner_password(&password);
assert!(result.is_ok());
assert!(!result.unwrap());
}
}
#[test]
fn test_encryption_handler_thread_safety_simulation() {
let dict = create_test_encryption_dict();
let handler = EncryptionHandler::new(&dict, None).unwrap();
for _ in 0..100 {
assert!(!handler.is_unlocked());
assert_eq!(handler.algorithm_info(), "RC4 40-bit");
assert!(handler.encrypt_strings());
assert!(handler.encrypt_streams());
assert!(handler.encrypt_metadata());
}
}
#[test]
fn test_encryption_state_transitions() {
let dict = create_test_encryption_dict();
let mut handler = EncryptionHandler::new(&dict, None).unwrap();
assert!(!handler.is_unlocked());
let result = handler.try_empty_password().unwrap();
assert!(!result);
assert!(!handler.is_unlocked());
let result = handler.unlock_with_user_password("test").unwrap();
assert!(!result);
assert!(!handler.is_unlocked());
let result = handler.unlock_with_owner_password("test").unwrap();
assert!(!result);
assert!(!handler.is_unlocked());
assert!(!handler.is_unlocked());
}
#[test]
fn test_interactive_decryption_edge_cases() {
let provider = MockPasswordProvider {
user_password: None,
owner_password: None,
};
let decryption = InteractiveDecryption::new(provider);
let dict = create_test_encryption_dict();
let mut handler = EncryptionHandler::new(&dict, None).unwrap();
let result = decryption.unlock_pdf(&mut handler).unwrap();
matches!(result, PasswordResult::Cancelled);
let provider = MockPasswordProvider {
user_password: Some("".to_string()),
owner_password: Some("".to_string()),
};
let decryption = InteractiveDecryption::new(provider);
let mut handler = EncryptionHandler::new(&dict, None).unwrap();
let result = decryption.unlock_pdf(&mut handler).unwrap();
matches!(result, PasswordResult::Rejected);
}
struct EdgeCasePasswordProvider {
call_count: std::cell::RefCell<usize>,
passwords: Vec<Option<String>>,
}
impl EdgeCasePasswordProvider {
fn new(passwords: Vec<Option<String>>) -> Self {
Self {
call_count: std::cell::RefCell::new(0),
passwords,
}
}
}
impl PasswordProvider for EdgeCasePasswordProvider {
fn prompt_user_password(&self) -> ParseResult<Option<String>> {
let mut count = self.call_count.borrow_mut();
if *count < self.passwords.len() {
let result = self.passwords[*count].clone();
*count += 1;
Ok(result)
} else {
Ok(None)
}
}
fn prompt_owner_password(&self) -> ParseResult<Option<String>> {
self.prompt_user_password()
}
}
#[test]
fn test_interactive_decryption_with_sequence() {
let passwords = vec![
Some("first_attempt".to_string()),
Some("second_attempt".to_string()),
None, ];
let provider = EdgeCasePasswordProvider::new(passwords);
let decryption = InteractiveDecryption::new(provider);
let dict = create_test_encryption_dict();
let mut handler = EncryptionHandler::new(&dict, None).unwrap();
let result = decryption.unlock_pdf(&mut handler).unwrap();
matches!(result, PasswordResult::Cancelled);
}
}