mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-31 21:48:09 -06:00
M11: /Crypt doesn't count against with_direct_parms' chain limit
The decoders leave /Crypt out wherever it stands (Stream::filters), so a chain of /Crypt and eight other filters decodes, but with_direct_parms counted nine and left its parameters as they were: a predictor given by reference was then skipped. Only the other filters count now, and a /Crypt entry is null in the copy, as no decoder reads it. Tests cover that chain and one exactly as long as a decoder takes. From reviews with the pdfcraft-review skill. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P6yUtvWGPsJpMLyh6pxSXG
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
58945d1f5b
commit
e6fddf155b
@@ -162,18 +162,20 @@ impl Document {
|
||||
/// an indirect dictionary resolved, and of an array (itself perhaps indirect), the entries at
|
||||
/// the filters' places (`/Crypt` counts, ISO 32000-2 §7.4.1) resolved, the rest dropped. Of
|
||||
/// each dictionary only the entries the decoders read are kept, numbers and flags given by
|
||||
/// reference (`/Predictor 7 0 R`) resolved; `/JBIG2Globals` stays a reference. `s` itself
|
||||
/// when none of those is indirect, or when the chain is longer than any decoder takes
|
||||
/// reference (`/Predictor 7 0 R`) resolved; `/JBIG2Globals` stays a reference. A `/Crypt`
|
||||
/// entry, which no decoder reads ([`Stream::filters`] leaves it out), is null. `s` itself
|
||||
/// when none of those is indirect, or when the other filters are more than any decoder takes
|
||||
/// ([`pdfcraft_filters::MAX_CHAIN`]). Only small values are copied, so a file can't have a
|
||||
/// large object copied once for each key and filter.
|
||||
pub fn with_direct_parms<'s>(&self, s: &'s Stream) -> Cow<'s, Stream> {
|
||||
let Some(p) = s.dict.get(b"DecodeParms") else { return Cow::Borrowed(s) };
|
||||
let filters = match s.dict.get(b"Filter") {
|
||||
Some(Object::Array(a)) => a.len(),
|
||||
Some(_) => 1,
|
||||
None => 0,
|
||||
let crypt = |f: &Object| matches!(f.as_name(), Some(b"Crypt"));
|
||||
let filters: Vec<bool> = match s.dict.get(b"Filter") {
|
||||
Some(Object::Array(a)) => a.iter().map(crypt).collect(),
|
||||
Some(f) => vec![crypt(f)],
|
||||
None => Vec::new(),
|
||||
};
|
||||
if filters > pdfcraft_filters::MAX_CHAIN {
|
||||
if filters.iter().filter(|c| !**c).count() > pdfcraft_filters::MAX_CHAIN {
|
||||
return Cow::Borrowed(s);
|
||||
}
|
||||
let mut changed = matches!(p, Object::Ref(_));
|
||||
@@ -196,7 +198,9 @@ impl Document {
|
||||
Object::Dict(made)
|
||||
};
|
||||
let made = match &*self.held(p) {
|
||||
Object::Array(a) => Object::Array(a.get(..filters.min(a.len())).unwrap_or_default().iter().map(|e| one(e, &mut changed)).collect()),
|
||||
Object::Array(a) => {
|
||||
Object::Array(a.iter().zip(&filters).map(|(e, crypt)| if *crypt { Object::Null } else { one(e, &mut changed) }).collect())
|
||||
}
|
||||
_ => one(p, &mut changed),
|
||||
};
|
||||
if !changed {
|
||||
|
||||
@@ -58,6 +58,25 @@ fn decode_parms_given_by_reference_are_read_once_direct() {
|
||||
assert_eq!(made.dict.get(b"DecodeParms").and_then(Object::as_array).map(Vec::len), Some(1));
|
||||
let long = stream(pdfcraft_filters::MAX_CHAIN + 1, Object::Array(vec![Object::Ref(r); 9]));
|
||||
assert!(matches!(doc.with_direct_parms(&long), Cow::Borrowed(_)));
|
||||
// A chain as long as a decoder takes is resolved, and a `/Crypt` in it doesn't count, as
|
||||
// the decoders leave it out: ASCIIHex as often as the limit allows, then the predictor's
|
||||
// Flate, its parameters by reference.
|
||||
let hex = |b: &[u8]| b.iter().flat_map(|x| format!("{x:02X}").into_bytes()).chain(*b">").collect::<Vec<u8>>();
|
||||
let wrapped = (1..pdfcraft_filters::MAX_CHAIN).fold(coded.clone(), |data, _| hex(&data));
|
||||
for crypt in [false, true] {
|
||||
let mut names = vec![Object::name("ASCIIHexDecode"); pdfcraft_filters::MAX_CHAIN - 1];
|
||||
names.push(Object::name("FlateDecode"));
|
||||
if crypt {
|
||||
names.insert(0, Object::name("Crypt"));
|
||||
}
|
||||
let mut entries = vec![Object::Null; names.len() - 1];
|
||||
entries.push(Object::Ref(r));
|
||||
let mut d = Dict::new();
|
||||
d.set(b"Filter".to_vec(), Object::Array(names));
|
||||
d.set(b"DecodeParms".to_vec(), Object::Array(entries));
|
||||
let s = Stream::from_raw(d, wrapped.clone());
|
||||
assert_eq!(doc.with_direct_parms(&s).decoded().unwrap(), rows, "with /Crypt: {crypt}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user