mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-27 11:37:59 -06:00
fix(xfdf): a field name full of periods no longer overflows the stack on export
The XFDF export nested one <field> element per period-separated part of a field's full name. A /T can hold periods of its own, so a name can have far more parts than the field tree has levels. Past 64 levels the import refuses the file the export wrote, and around 100,000 parts the recursive write overflows the main thread's stack and aborts the process. The export now nests at most as many levels as the import reads back. The parts past the last level stay in the deepest element's name, and the import joins them back into the same full name.
This commit is contained in:
@@ -415,7 +415,13 @@ fn xfdf_fields(doc: &Document, out: &mut String) {
|
||||
continue;
|
||||
}
|
||||
let mut node = &mut root;
|
||||
for part in f.name.split('.') {
|
||||
// One `<field>` level per part of the name, but no more levels than the import reads
|
||||
// back. A `/T` can hold periods of its own, so a name can split into far more parts than
|
||||
// the field tree has levels: nesting every part wrote a file the import refuses and, for
|
||||
// a long enough name, recursed in `write_node` until the stack overflowed. The parts past
|
||||
// the last level stay together in the deepest element's name, and the import joins them
|
||||
// back with periods into the same full name.
|
||||
for part in f.name.splitn(MAX_DEPTH, '.') {
|
||||
let i = match node.kids.iter().position(|(k, _)| k == part) {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
|
||||
@@ -1104,6 +1104,45 @@ fn wide_or_commented_xml_is_not_mistaken_for_deep() {
|
||||
assert!(check_form_xml_depth(&nested(MAX_XML_DEPTH + 1)).is_err());
|
||||
}
|
||||
|
||||
/// [`one_field`] with its field's `/T` set to `name`, periods and all, holding `value`.
|
||||
fn one_field_named(name: &str, value: &str) -> Document {
|
||||
let mut doc = one_field();
|
||||
let r = fields(&doc).into_iter().find(|f| f.name == "Name").unwrap().obj;
|
||||
doc.update_dict(r, |d| {
|
||||
d.set(b"T".to_vec(), Object::String(PdfString::text(name)));
|
||||
d.set(b"V".to_vec(), Object::String(PdfString::text(value)));
|
||||
})
|
||||
.unwrap();
|
||||
doc
|
||||
}
|
||||
|
||||
/// A `/T` can hold periods of its own, so a field name can split into more parts than the field
|
||||
/// tree has levels. The XFDF export nested one `<field>` per part, and the import refuses more
|
||||
/// than 64 levels, so the form's own export could not be read back.
|
||||
#[test]
|
||||
fn field_names_with_more_parts_than_the_import_reads_still_round_trip() {
|
||||
for parts in [1, 2, 63, 64, 65, 66, 100, 1_000] {
|
||||
let name = (0..parts).map(|i| format!("p{i}")).collect::<Vec<_>>().join(".");
|
||||
let doc = one_field_named(&name, "v");
|
||||
assert_eq!(values(&doc), vec![(name.clone(), vec!["v".to_string()])], "{parts} parts");
|
||||
let xfdf = export_xfdf(&doc, false, true, "form.pdf");
|
||||
assert_eq!(data_values(xfdf.as_bytes()), Ok(vec![(name.clone(), vec!["v".to_string()])]), "{parts} parts");
|
||||
let mut back = one_field_named(&name, "old");
|
||||
let report = import(&mut back, xfdf.as_bytes()).unwrap();
|
||||
assert_eq!((report.fields, values(&back)), (1, vec![(name.clone(), vec!["v".to_string()])]), "{parts} parts");
|
||||
}
|
||||
}
|
||||
|
||||
/// A name of a hundred thousand parts nested as deep, and writing it out recursed once per level
|
||||
/// until the stack overflowed, which aborts the process.
|
||||
#[test]
|
||||
fn a_field_name_of_many_parts_exports_without_overflowing_the_stack() {
|
||||
let name = vec!["p"; 100_000].join(".");
|
||||
let want = vec![(name.clone(), vec!["v".to_string()])];
|
||||
let read = on_small_stack(move || data_values(export_xfdf(&one_field_named(&name, "v"), false, true, "form.pdf").as_bytes()));
|
||||
assert_eq!(read, Ok(want));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_xfdf_annotation_flags_do_not_turn_on_unrelated_bits() {
|
||||
// Flags are a set of bit masks, not additive integers. A repeated "print" flag
|
||||
|
||||
Reference in New Issue
Block a user