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,
Aes128,
Aes256,
}
impl EncryptionAlgorithm {
pub fn key_length(&self) -> usize {
match self {
Self::Rc4_40 => 5,
Self::Rc4_128 => 16,
Self::Aes128 => 16,
Self::Aes256 => 32,
}
}
pub fn name(&self) -> &str {
match self {
Self::Rc4_40 => "V2",
Self::Rc4_128 => "V4",
Self::Aes128 => "AESV2",
Self::Aes256 => "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 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::Aes256 {
"5"
} else if self.encryption_algorithm == EncryptionAlgorithm::Aes128 {
"4"
} else {
"2"
},
if self.encryption_algorithm == EncryptionAlgorithm::Aes256 {
"5"
} else if self.encryption_algorithm == EncryptionAlgorithm::Aes128 {
"4"
} else {
"3"
},
key_length,
flags,
if self.encrypt_metadata { "true" } else { "false" }
)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DigitalSignature {
pub signer_name: String,
pub reason: Option<String>,
pub location: Option<String>,
pub contact_info: Option<String>,
pub date: Option<String>,
pub filter: String,
pub sub_filter: String,
pub signature_hex: Option<String>,
pub byte_range: Option<Vec<u32>>,
}
impl Default for DigitalSignature {
fn default() -> Self {
Self {
signer_name: String::new(),
reason: None,
location: None,
contact_info: None,
date: None,
filter: "Adobe.PPKLite".to_string(),
sub_filter: "adbe.pkcs7.detached".to_string(),
signature_hex: None,
byte_range: None,
}
}
}
impl DigitalSignature {
pub fn new(signer_name: impl Into<String>) -> Self {
Self {
signer_name: signer_name.into(),
..Default::default()
}
}
pub fn with_reason(mut self, reason: impl Into<String>) -> Self {
self.reason = Some(reason.into());
self
}
pub fn with_location(mut self, location: impl Into<String>) -> Self {
self.location = Some(location.into());
self
}
pub fn with_contact_info(mut self, contact: impl Into<String>) -> Self {
self.contact_info = Some(contact.into());
self
}
pub fn with_date(mut self, date: impl Into<String>) -> Self {
self.date = Some(date.into());
self
}
pub fn to_pdf_dict(&self) -> String {
let mut dict = format!(
"<< /Type /Sig\n\
/Filter /{}\n\
/SubFilter /{}\n\
/M (D:{})\n\
/Name ({})\n",
escape_pdf_name(&self.filter),
escape_pdf_name(&self.sub_filter),
self.date.as_deref().unwrap_or(""),
escape_pdf_string(&self.signer_name)
);
if let Some(ref reason) = self.reason {
dict.push_str(&format!(" /Reason ({})\n", escape_pdf_string(reason)));
}
if let Some(ref location) = self.location {
dict.push_str(&format!(" /Location ({})\n", escape_pdf_string(location)));
}
if let Some(ref contact) = self.contact_info {
dict.push_str(&format!(" /ContactInfo ({})\n", escape_pdf_string(contact)));
}
if let Some(ref range) = self.byte_range {
let range_str: Vec<String> = range.iter().map(|v| v.to_string()).collect();
dict.push_str(&format!(" /ByteRange [{}]\n", range_str.join(" ")));
}
if let Some(ref sig_hex) = self.signature_hex {
dict.push_str(&format!(" /Contents <{}>\n", sig_hex));
} else {
dict.push_str(" /Contents <");
dict.push_str(&"0".repeat(8192));
dict.push_str(">\n");
}
dict.push_str(">>");
dict
}
}
fn escape_pdf_string(text: &str) -> String {
text.replace('\\', "\\\\")
.replace('(', "\\(")
.replace(')', "\\)")
}
fn escape_pdf_name(name: &str) -> String {
name.replace(" ", "#20")
.replace("#", "#23")
.replace("/", "#2F")
.replace("[", "#5B")
.replace("]", "#5D")
.replace("<", "#3C")
.replace(">", "#3E")
.replace("(", "#28")
.replace(")", "#29")
}
#[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::Aes128.key_length(), 16);
assert_eq!(EncryptionAlgorithm::Aes256.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 <"));
}
#[test]
fn test_digital_signature_defaults() {
let sig = DigitalSignature::new("Alice");
assert_eq!(sig.signer_name, "Alice");
assert_eq!(sig.filter, "Adobe.PPKLite");
assert_eq!(sig.sub_filter, "adbe.pkcs7.detached");
}
#[test]
fn test_digital_signature_builder() {
let sig = DigitalSignature::new("Bob")
.with_reason("I approve")
.with_location("NYC")
.with_contact_info("bob@example.com")
.with_date("20240101");
assert_eq!(sig.signer_name, "Bob");
assert_eq!(sig.reason, Some("I approve".to_string()));
assert_eq!(sig.location, Some("NYC".to_string()));
assert_eq!(sig.contact_info, Some("bob@example.com".to_string()));
assert_eq!(sig.date, Some("20240101".to_string()));
}
#[test]
fn test_digital_signature_to_pdf_dict() {
let sig = DigitalSignature::new("Charlie")
.with_reason("Test reason")
.with_location("Test location");
let dict = sig.to_pdf_dict();
assert!(dict.contains("/Type /Sig"));
assert!(dict.contains("/Filter /Adobe.PPKLite"));
assert!(dict.contains("/SubFilter /adbe.pkcs7.detached"));
assert!(dict.contains("/Name (Charlie)"));
assert!(dict.contains("/Reason (Test reason)"));
assert!(dict.contains("/Location (Test location)"));
}
}