use anyhow::{anyhow, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PdfPermissions {
pub print: bool,
pub copy: bool,
pub modify: bool,
pub annotate: bool,
pub fill_forms: bool,
pub extract: bool,
pub assemble: bool,
pub print_high_quality: bool,
}
impl Default for PdfPermissions {
fn default() -> Self {
Self {
print: true,
copy: true,
modify: true,
annotate: true,
fill_forms: true,
extract: true,
assemble: true,
print_high_quality: true,
}
}
}
impl PdfPermissions {
pub fn all() -> Self {
Self::default()
}
pub fn none() -> Self {
Self {
print: false,
copy: false,
modify: false,
annotate: false,
fill_forms: false,
extract: false,
assemble: false,
print_high_quality: false,
}
}
pub fn read_only() -> Self {
Self {
print: false,
copy: false,
modify: false,
annotate: false,
fill_forms: false,
extract: true,
assemble: false,
print_high_quality: false,
}
}
pub fn to_pdf_flags(&self) -> u32 {
let mut flags = 0xFFFFF0C0u32;
flags &= !(1 << 2); flags &= !(1 << 3); flags &= !(1 << 4); flags &= !(1 << 5); flags &= !(1 << 8); flags &= !(1 << 9); flags &= !(1 << 11); flags &= !(1 << 12);
if self.print {
flags |= 1 << 2;
}
if self.modify {
flags |= 1 << 3;
}
if self.copy {
flags |= 1 << 4;
}
if self.annotate {
flags |= 1 << 5;
}
if self.fill_forms {
flags |= 1 << 8;
}
if self.extract {
flags |= 1 << 9;
}
if self.assemble {
flags |= 1 << 11;
}
if self.print_high_quality {
flags |= 1 << 12;
}
flags
}
pub fn from_pdf_flags(flags: u32) -> Self {
Self {
print: (flags & (1 << 2)) != 0,
modify: (flags & (1 << 3)) != 0,
copy: (flags & (1 << 4)) != 0,
annotate: (flags & (1 << 5)) != 0,
fill_forms: (flags & (1 << 8)) != 0,
extract: (flags & (1 << 9)) != 0,
assemble: (flags & (1 << 11)) != 0,
print_high_quality: (flags & (1 << 12)) != 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EncryptionAlgorithm {
Rc4_40,
Rc4_128,
Aes_128,
Aes_256,
}
impl EncryptionAlgorithm {
pub fn key_length(&self) -> usize {
match self {
Self::Rc4_40 => 5,
Self::Rc4_128 => 16,
Self::Aes_128 => 16,
Self::Aes_256 => 32,
}
}
pub fn name(&self) -> &str {
match self {
Self::Rc4_40 => "V2",
Self::Rc4_128 => "V4",
Self::Aes_128 => "AESV2",
Self::Aes_256 => "AESV3",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PdfSecurity {
pub user_password: Option<String>,
pub owner_password: Option<String>,
pub encryption_algorithm: EncryptionAlgorithm,
pub permissions: PdfPermissions,
pub encrypt_metadata: bool,
}
impl Default for PdfSecurity {
fn default() -> Self {
Self {
user_password: None,
owner_password: None,
encryption_algorithm: EncryptionAlgorithm::Rc4_128,
permissions: PdfPermissions::default(),
encrypt_metadata: true,
}
}
}
impl PdfSecurity {
pub fn new() -> Self {
Self::default()
}
pub fn with_user_password(mut self, password: String) -> Self {
self.user_password = Some(password);
self
}
pub fn with_owner_password(mut self, password: String) -> Self {
self.owner_password = Some(password);
self
}
pub fn with_encryption(mut self, algorithm: EncryptionAlgorithm) -> Self {
self.encryption_algorithm = algorithm;
self
}
pub fn with_permissions(mut self, permissions: PdfPermissions) -> Self {
self.permissions = permissions;
self
}
pub fn with_encrypt_metadata(mut self, encrypt: bool) -> Self {
self.encrypt_metadata = encrypt;
self
}
pub fn is_protected(&self) -> bool {
self.user_password.is_some() || self.owner_password.is_some()
}
pub fn validate(&self) -> Result<()> {
if self.user_password.is_some() && self.user_password.as_ref().unwrap().is_empty() {
return Err(anyhow!("User password cannot be empty"));
}
if self.owner_password.is_some() && self.owner_password.as_ref().unwrap().is_empty() {
return Err(anyhow!("Owner password cannot be empty"));
}
Ok(())
}
}
impl PdfSecurity {
pub fn encrypt_data(&self, data: &[u8], _key: &[u8]) -> Result<Vec<u8>> {
if !self.is_protected() {
return Ok(data.to_vec());
}
Ok(data.to_vec())
}
pub fn decrypt_data(&self, data: &[u8], _key: &[u8]) -> Result<Vec<u8>> {
if !self.is_protected() {
return Ok(data.to_vec());
}
Ok(data.to_vec())
}
pub fn generate_encryption_key(&self) -> Result<Vec<u8>> {
if !self.is_protected() {
return Ok(Vec::new());
}
let key_len = self.encryption_algorithm.key_length();
Ok(vec![0u8; key_len])
}
pub fn create_encryption_dict(&self) -> String {
if !self.is_protected() {
return String::new();
}
let algorithm = self.encryption_algorithm.name();
let key_length = self.encryption_algorithm.key_length() * 8;
let flags = self.permissions.to_pdf_flags();
format!(
"<< /Filter /Standard \
/V {} \
/R {} \
/Length {} \
/P {} \
/EncryptMetadata {} \
/O <OWNER_PASSWORD_PLACEHOLDER> \
/U <USER_PASSWORD_PLACEHOLDER> >>",
if self.encryption_algorithm == EncryptionAlgorithm::Aes_256 {
"5"
} else if self.encryption_algorithm == EncryptionAlgorithm::Aes_128 {
"4"
} else {
"2"
},
if self.encryption_algorithm == EncryptionAlgorithm::Aes_256 {
"5"
} else if self.encryption_algorithm == EncryptionAlgorithm::Aes_128 {
"4"
} else {
"3"
},
key_length,
flags,
if self.encrypt_metadata { "true" } else { "false" }
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_permissions_default() {
let perms = PdfPermissions::default();
assert!(perms.print);
assert!(perms.copy);
assert!(perms.modify);
}
#[test]
fn test_permissions_none() {
let perms = PdfPermissions::none();
assert!(!perms.print);
assert!(!perms.copy);
assert!(!perms.modify);
}
#[test]
fn test_permissions_read_only() {
let perms = PdfPermissions::read_only();
assert!(!perms.print);
assert!(!perms.copy);
assert!(perms.extract);
}
#[test]
fn test_permissions_flags_roundtrip() {
let perms = PdfPermissions {
print: true,
copy: false,
modify: true,
annotate: false,
fill_forms: true,
extract: false,
assemble: true,
print_high_quality: false,
};
let flags = perms.to_pdf_flags();
let restored = PdfPermissions::from_pdf_flags(flags);
assert_eq!(restored.print, perms.print);
assert_eq!(restored.copy, perms.copy);
assert_eq!(restored.modify, perms.modify);
assert_eq!(restored.annotate, perms.annotate);
assert_eq!(restored.fill_forms, perms.fill_forms);
assert_eq!(restored.extract, perms.extract);
assert_eq!(restored.assemble, perms.assemble);
assert_eq!(restored.print_high_quality, perms.print_high_quality);
}
#[test]
fn test_encryption_algorithm_key_length() {
assert_eq!(EncryptionAlgorithm::Rc4_40.key_length(), 5);
assert_eq!(EncryptionAlgorithm::Rc4_128.key_length(), 16);
assert_eq!(EncryptionAlgorithm::Aes_128.key_length(), 16);
assert_eq!(EncryptionAlgorithm::Aes_256.key_length(), 32);
}
#[test]
fn test_security_default() {
let security = PdfSecurity::new();
assert!(!security.is_protected());
assert!(security.validate().is_ok());
}
#[test]
fn test_security_with_user_password() {
let security = PdfSecurity::new()
.with_user_password("test123".to_string());
assert!(security.is_protected());
assert!(security.validate().is_ok());
}
#[test]
fn test_security_empty_password_rejected() {
let security = PdfSecurity::new()
.with_user_password("".to_string());
assert!(security.validate().is_err());
}
#[test]
fn test_security_read_only() {
let perms = PdfPermissions::read_only();
let security = PdfSecurity::new()
.with_user_password("secret".to_string())
.with_permissions(perms);
assert!(security.is_protected());
assert!(!security.permissions.copy);
assert!(!security.permissions.modify);
}
#[test]
fn test_create_encryption_dict() {
let security = PdfSecurity::new()
.with_user_password("user".to_string())
.with_owner_password("owner".to_string());
let dict = security.create_encryption_dict();
assert!(dict.contains("/Filter /Standard"));
assert!(dict.contains("/O <"));
assert!(dict.contains("/U <"));
}
}