use lopdf::{Dictionary, Document, Object};
use super::SignatureSummary;
pub(crate) fn resolve<'a>(doc: &'a Document, obj: &'a Object) -> &'a Object {
match obj {
Object::Reference(id) => doc.get_object(*id).unwrap_or(obj),
other => other,
}
}
pub(crate) fn resolve_dict<'a>(doc: &'a Document, obj: &'a Object) -> Option<&'a Dictionary> {
match resolve(doc, obj) {
Object::Dictionary(d) => Some(d),
_ => None,
}
}
fn catalog(doc: &Document) -> Option<&Dictionary> {
let root = doc.trailer.get(b"Root").ok()?;
resolve_dict(doc, root)
}
pub(crate) fn detect_signatures(doc: &Document) -> Vec<SignatureSummary> {
let mut found = Vec::new();
let Some(cat) = catalog(doc) else {
return found;
};
let Some(acroform) = cat.get(b"AcroForm").ok().and_then(|o| resolve_dict(doc, o)) else {
return found;
};
let Some(fields) = acroform.get(b"Fields").ok().map(|o| resolve(doc, o)) else {
return found;
};
let Object::Array(fields) = fields else {
return found;
};
let mut stack: Vec<&Object> = fields.iter().collect();
while let Some(field_obj) = stack.pop() {
let Some(field) = resolve_dict(doc, field_obj) else {
continue;
};
if let Ok(Object::Array(kids)) = field.get(b"Kids").map(|o| resolve(doc, o)) {
stack.extend(kids.iter());
}
let is_sig = matches!(
field.get(b"FT").map(|o| resolve(doc, o)),
Ok(Object::Name(n)) if n == b"Sig"
);
if !is_sig {
continue;
}
let Some(value) = field.get(b"V").ok().and_then(|o| resolve_dict(doc, o)) else {
continue; };
let field_name = match field.get(b"T").map(|o| resolve(doc, o)) {
Ok(Object::String(s, _)) => String::from_utf8_lossy(s).to_string(),
_ => String::new(),
};
let docmdp_permission = docmdp_level(doc, value);
found.push(SignatureSummary {
field_name,
docmdp_permission,
});
}
found
}
fn docmdp_level(doc: &Document, sig_value: &Dictionary) -> Option<u32> {
let refs = match sig_value.get(b"Reference").map(|o| resolve(doc, o)) {
Ok(Object::Array(a)) => a,
_ => return None,
};
for entry in refs {
let Some(sig_ref) = resolve_dict(doc, entry) else {
continue;
};
let is_docmdp = matches!(
sig_ref.get(b"TransformMethod").map(|o| resolve(doc, o)),
Ok(Object::Name(n)) if n == b"DocMDP"
);
if !is_docmdp {
continue;
}
let Some(params) = sig_ref
.get(b"TransformParams")
.ok()
.and_then(|o| resolve_dict(doc, o))
else {
return Some(2);
};
return match params.get(b"P").map(|o| resolve(doc, o)) {
Ok(Object::Integer(p)) => Some(*p as u32),
_ => Some(2),
};
}
None
}
pub(crate) fn modification_forbidden(doc: &Document) -> bool {
let Ok(encrypt) = doc.trailer.get(b"Encrypt") else {
return false;
};
let Some(enc) = resolve_dict(doc, encrypt) else {
return false;
};
match enc.get(b"P").map(|o| resolve(doc, o)) {
Ok(Object::Integer(p)) => *p & 8 == 0,
_ => false,
}
}