fix(xfa): strip reads a shared indirect Kids array once

This commit is contained in:
Dylan M. Taylor committed 2026-11-04 03:11:25 +00:00
1 parent 9ce6d89f46
commit 25d23ed4a1
1 file changed
+35 -1
+35 -1
View File
@@ -151,6 +151,7 @@ pub fn strip(doc: &mut Document) -> Result<Stripped, XfaError> {
let fields = acro.get(b"Fields").map(|f| doc.resolve(f)).and_then(|f| f.as_array().cloned()).unwrap_or_default();
let mut seen = std::collections::HashSet::new();
let mut todo: Vec<ObjRef> = Vec::new();
let mut kids_seen = std::collections::HashSet::new();
for r in fields.iter().filter_map(Object::as_ref) {
if seen.insert(r) && seen.len() <= MAX_STRIP_FIELDS {
todo.push(r);
@@ -174,7 +175,9 @@ pub fn strip(doc: &mut Document) -> Result<Stripped, XfaError> {
}
})?;
}
if let Some(kids) = d.get(b"Kids").map(|k| doc.resolve(k)).and_then(|k| k.as_array().cloned()) {
// An indirect /Kids array several fields share is read once: its entries are already queued.
let shared_seen = d.get(b"Kids").and_then(Object::as_ref).is_some_and(|k| !kids_seen.insert(k));
if !shared_seen && let Some(kids) = d.get(b"Kids").map(|k| doc.resolve(k)).and_then(|k| k.as_array().cloned()) {
for k in kids.iter().filter_map(Object::as_ref) {
if seen.insert(k) && seen.len() <= MAX_STRIP_FIELDS {
todo.push(k);
@@ -280,3 +283,34 @@ pub fn existing_layout(doc: &Document) -> Option<crate::Report> {
warnings,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fields_sharing_a_kids_array_still_lose_their_private_keys_once() {
let mut doc = Document::new_empty();
let mut kid = Dict::new();
kid.set(CLICK_KEY.to_vec(), Object::Int(1));
let kid = doc.add(Object::Dict(kid));
let kids = doc.add(Object::Array(vec![Object::Ref(kid)]));
let parents: Vec<Object> = (0..3)
.map(|_| {
let mut p = Dict::new();
p.set(b"Kids".to_vec(), Object::Ref(kids));
Object::Ref(doc.add(Object::Dict(p)))
})
.collect();
let mut acro = Dict::new();
acro.set(b"XFA".to_vec(), Object::Array(Vec::new()));
acro.set(b"Fields".to_vec(), Object::Array(parents));
let root = doc.root().unwrap();
let mut catalog = doc.get(root).as_dict().cloned().unwrap();
catalog.set(b"AcroForm".to_vec(), Object::Dict(acro));
doc.set(root, Object::Dict(catalog));
let out = strip(&mut doc).unwrap();
assert_eq!(out.script_buttons, 1);
assert!(!doc.get(kid).as_dict().unwrap().contains(CLICK_KEY));
}
}