use anyhow::{Context, Result, bail};
use bitflags::Flags;
use env_logger::Env;
use log::{debug, info};
use lopdf::{Document, Permissions};
use pdf_perm::{PdfPerm, ShortFlags};
use std::{io::Write, path::Path};
fn main() -> Result<()> {
setup_logger();
let mut iter = std::env::args();
let program_path = iter.next().unwrap_or_else(|| "pdf-perm".to_string());
let args: Vec<_> = iter.collect();
let mut perm_mod = None;
if is_desec(&program_path) {
let input_path = match args.into_iter().next() {
Some(path) => path,
None => {
println!("DeSec mode activated!");
println!("Usage: {program_path} <INPUT>");
prompt_input_path()?
}
};
return set_permissions(&input_path, &input_path, Some("=*"));
}
let (input_path, output_path) = match args.len() {
0 => {
println!("Usage: {program_path} [PERMISSION] <INPUT> [OUTPUT]\n");
println!("Supported permissions: {}", Permissions::all().summary());
display(Permissions::all());
println!("\nYou can use * to represent all permissions.");
return Ok(());
}
1 => (&args[0], &args[0]), 2 => {
perm_mod.replace(&args[0]);
(&args[1], &args[1])
}
3 => {
perm_mod.replace(&args[0]);
(&args[1], &args[2])
}
_ => bail!("Too many arguments"),
};
set_permissions(input_path, output_path, perm_mod.map(|x| x.as_str()))
}
fn set_permissions(input_path: &str, output_path: &str, perm_mod: Option<&str>) -> Result<()> {
info!("Reading document: {input_path}");
let mut doc = Document::load(input_path)?;
debug!("Encryption state: {:?}", doc.encryption_state);
let mut perm = doc.permissions();
info!("Original permissions: {}", perm.summary());
let Some(perm_mod) = perm_mod else {
info!("No modifications specified, exiting");
return Ok(());
};
perm.apply_modification(perm_mod);
info!("Modified permissions: {}", perm.summary());
doc.set_permissions(perm)
.with_context(|| format!("Failed to set permissions for given document: {input_path}"))?;
info!("Saving document to {output_path}");
doc.save(output_path)
.with_context(|| format!("Failed to save document: {output_path}"))?;
Ok(())
}
fn setup_logger() {
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
.format(|buf, record| {
let level = record.level();
let style = buf.default_level_style(level);
writeln!(buf, "[{style}{level}{style:#}] {}", record.args())
})
.init();
}
fn display(permissions: Permissions) {
for (short, flag) in Permissions::SHORT_FLAGS.iter().zip(Permissions::FLAGS) {
let perm = flag.value();
let name = flag.name();
if permissions.contains(*perm) {
println!("+ [{short}] {name}");
} else {
println!("- [{short}] {name}");
}
}
}
fn is_desec(program_path: &str) -> bool {
let path = Path::new(program_path);
let file_name = path.file_stem().unwrap_or_default();
let program_name = file_name.to_str().unwrap_or_default().to_lowercase();
let program_name = program_name.strip_prefix("pdf-").unwrap_or(&program_name);
let program_name = program_name.strip_suffix("ure").unwrap_or(&program_name);
program_name == "desec"
}
fn prompt_input_path() -> Result<String> {
println!("Please enter the input file path:");
let mut input_path = String::new();
std::io::stdin()
.read_line(&mut input_path)
.with_context(|| format!("Failed to read from stdin"))?;
Ok(input_path)
}