diff --git a/crates/xlsx/src/tests/controls_tests.rs b/crates/xlsx/src/tests/controls_tests.rs
index a4f97439a..f8aa60d08 100644
--- a/crates/xlsx/src/tests/controls_tests.rs
+++ b/crates/xlsx/src/tests/controls_tests.rs
@@ -232,3 +232,53 @@ fn the_properties_part_wins_over_the_vml() {
let (back, _) = read_xlsx(&zipped).unwrap();
assert_eq!(back.sheet(0).unwrap().controls[0].max, 500);
}
+
+/// A hostile sheet: more controls than a sheet keeps, properties parts that are not XML or hold
+/// numbers no control can take, shape ids that are not numbers, and oversized names, captions and
+/// links. Reading must not panic, must keep at most the cap, and every control it keeps must pass
+/// the model's rules.
+#[test]
+fn hostile_form_controls_are_capped_and_repaired() {
+ let n = 13_000;
+ let mut controls = String::from("");
+ let anchor = r#"1-99999999999999999999990-4020"#;
+ for i in 0..n {
+ let rid = match i % 10 {
+ 0 => "rIdB",
+ 1 => "rIdC",
+ _ => "rIdA",
+ };
+ let _ = std::fmt::Write::write_fmt(
+ &mut controls,
+ format_args!(r#"{anchor}"#, 2000 + i, "n".repeat(400)),
+ );
+ }
+ controls
+ .push_str(r#""#);
+ let rels = r#""#;
+ let link = format!("$B${}", "9".repeat(500));
+ let prop_a = format!(
+ r#""#
+ );
+ let body = format!(r#"{controls}"#)
+ .replace("", r#""#);
+ let bytes = minimal(
+ &body,
+ &[("xl/worksheets/_rels/sheet1.xml.rels", rels), ("xl/ctrlProps/a.xml", &prop_a), ("xl/ctrlProps/b.xml", "\u{0}\u{1}not xml <<<")],
+ "",
+ "",
+ );
+ let (wb, _) = read_xlsx(&bytes).unwrap();
+ let c = &wb.sheet(0).unwrap().controls;
+ assert_eq!(c.len(), 10_000, "the cap of one sheet");
+ for x in c {
+ assert_eq!(x.kind, ControlKind::Slider);
+ assert!(x.check_numbers().is_ok(), "{x:?}");
+ assert!(x.value >= x.min && x.value <= x.max, "{x:?}");
+ assert!(x.name.chars().count() <= 255 && x.link.is_none(), "{x:?}");
+ assert!(x.anchor.width >= 1.0 && x.anchor.height >= 1.0 && x.anchor.cell.row < gridcraft_core::MAX_ROWS, "{x:?}");
+ }
+ // Written back and read again, the kept controls stay valid.
+ let (back, _) = read_xlsx(&write_xlsx(&wb).unwrap()).unwrap();
+ assert_eq!(back.sheet(0).unwrap().controls.len(), c.len());
+}