mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-26 11:08:48 -06:00
fix(xfa): read a packet stream the XFA array lists again only once
The packet reader decoded the stream of every /XFA array entry and charged only the decoded output to its 64 MB budget, so one stream that decodes to almost nothing (Flate over hex whitespace), listed many times, repeated a large decode for free. A stream reference already read is skipped.
This commit is contained in:
@@ -113,7 +113,13 @@ pub fn read_packets(doc: &Document) -> Result<Option<Packets>, XfaError> {
|
||||
let needs_rendering = bool_of(catalog.as_dict().and_then(|c| c.get(b"NeedsRendering"))).or_else(|| bool_of(acro.get(b"NeedsRendering")));
|
||||
let has_fields = acro.get(b"Fields").map(|f| doc.resolve(f)).and_then(|f| f.as_array().map(|a| !a.is_empty())).unwrap_or(false);
|
||||
let mut total = 0usize;
|
||||
// A stream listed again adds nothing but another decode, which may cost far more than the
|
||||
// bytes it yields (a Flate stream of hex whitespace).
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
let mut read = |o: &Object| -> Result<Option<String>, XfaError> {
|
||||
if o.as_ref().is_some_and(|r| !seen.insert(r)) {
|
||||
return Ok(None);
|
||||
}
|
||||
let s = doc.resolve(o);
|
||||
let Object::Stream(s) = &*s else { return Ok(None) };
|
||||
let bytes = s.decoded_within(MAX_XDP)?;
|
||||
|
||||
@@ -416,6 +416,23 @@ fn empty_repeating_subforms_spend_the_layout_budget() {
|
||||
assert!(layout_xml(&tpl).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_packet_stream_listed_many_times_is_read_once() {
|
||||
let mut doc = Document::open(Arc::new(shell("<template><subform/></template>"))).unwrap();
|
||||
let packet = pdfcraft_cos::ObjRef::new(5, 0);
|
||||
let items: Vec<Object> =
|
||||
(0..1000).flat_map(|_| [Object::String(pdfcraft_cos::PdfString::literal(b"template".to_vec())), Object::Ref(packet)]).collect();
|
||||
let root = doc.root().unwrap();
|
||||
doc.update_dict(root, |c| {
|
||||
let mut acro = c.get(b"AcroForm").and_then(Object::as_dict).cloned().unwrap();
|
||||
acro.set(b"XFA".to_vec(), Object::Array(items));
|
||||
c.set(b"AcroForm".to_vec(), Object::Dict(acro));
|
||||
})
|
||||
.unwrap();
|
||||
let p = read_packets(&doc).unwrap().unwrap();
|
||||
assert_eq!(p.xdp.matches("<template").count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_packets_can_be_one_stream_or_named_parts() {
|
||||
// Named parts, with the template split from the rest.
|
||||
|
||||
Reference in New Issue
Block a user