M12: an XFA template parsed directly is held to the packet size limit

parse() refuses a template larger than the 64 MiB a document's packets may
hold (read_packets' limit), before any of it is parsed, so what parsing
keeps of it, item lists held by the field and its properties among them,
stays within a few times that whoever calls it (review).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pq7HyCfwzyRXj4VfQAVVdv
This commit is contained in:
mtlcl410andClaude Opus 5.5 committed 2026-10-30 20:51:57 +08:00
1 parent 17ad260d71
commit 0481b98cda
3 files changed
+26 -2

No files matched your search

+3 -2
View File
@@ -5,8 +5,9 @@ use pdfcraft_cos::{Document, Object};
use crate::XfaError;
/// Most decoded packet bytes read in total.
const MAX_XDP: usize = 64 << 20;
/// Most decoded packet bytes read in total; a template handed to [`crate::parse`] directly is held
/// to the same.
pub(crate) const MAX_XDP: usize = 64 << 20;
/// What was found in the XFA entry.
#[derive(Clone, Debug, Default, PartialEq)]
+10
View File
@@ -969,6 +969,16 @@ fn collect_page_roles(nodes: &[Node], out: &mut Vec<(String, PageRole)>, depth:
/// Parse a template packet. Returns the template and the warnings met on the way.
pub fn parse(xml: &str) -> Result<(Template, Vec<String>), XfaError> {
// As read from a document ([`crate::read_packets`]): a template parsed directly is no bigger,
// so what parsing keeps of it (item lists are held by the field and its properties) stays
// within a few times that.
if xml.len() > crate::packets::MAX_XDP {
return Err(XfaError::TooLarge(format!(
"the template is {} MiB, past the {} MiB the reader accepts",
xml.len() >> 20,
crate::packets::MAX_XDP >> 20
)));
}
let depth = crate::xml_nesting_depth(xml);
if depth > crate::MAX_XML_DEPTH {
return Err(XfaError::Malformed(format!(
+13
View File
@@ -221,3 +221,16 @@ fn a_caption_s_rich_text_and_a_list_s_items_are_kept() {
assert_eq!(f.common.props.elements[2].attrs, [("save".to_string(), "1".to_string())]);
assert_eq!(f.items.len(), 400, "the list shows the same entries");
}
/// A template handed to `parse` directly is held to the bytes a document's packets may hold: one
/// past them is refused before any of it is parsed, so the item lists a field and its properties
/// keep of it can't grow past a few times that.
#[test]
fn a_template_past_the_packet_limit_is_refused_before_parsing() {
let limit = crate::packets::MAX_XDP;
let head = r#"<template xmlns="http://www.xfa.org/schema/xfa-template/3.3/"><subform name="form"><field name="f"><items><text>"#;
let tail = "</text></items></field></subform></template>";
let xml = format!("{head}{}{tail}", "a".repeat(limit + 1 - head.len() - tail.len()));
assert_eq!(xml.len(), limit + 1);
assert!(matches!(crate::parse(&xml), Err(crate::XfaError::TooLarge(_))));
}