xlsx: warn when hyperlinks past the 10,000-cell cap are dropped on read (#513)

This commit is contained in:
MarekSpodBiedry
2026-10-12 08:39:12 +02:00
parent 3926d21aa9
commit 8517c7c48d
2 changed files with 19 additions and 2 deletions
+3 -2
View File
@@ -104,7 +104,7 @@ pub fn read_sheet(cx: &mut Ctx<'_>, part: &str, name: &str) -> Result<Sheet, IoE
}
}
}
"hyperlinks" => read_hyperlinks(e, &rels, &mut sheet),
"hyperlinks" => read_hyperlinks(cx, name, e, &rels, &mut sheet),
"autoFilter" => sheet.autofilter = read_autofilter(cx, e),
"printOptions" => {
sheet.print.gridlines = e.flag("gridLines", false);
@@ -709,7 +709,7 @@ fn read_validation(v: &El) -> Option<Validation> {
})
}
fn read_hyperlinks(e: &El, rels: &[Rel], sheet: &mut Sheet) {
fn read_hyperlinks(cx: &mut Ctx<'_>, name: &str, e: &El, rels: &[Rel], sheet: &mut Sheet) {
let mut cells = 0u64;
for h in e.kids("hyperlink") {
let Some(range) = h.attr("ref").and_then(RangeRef::parse) else { continue };
@@ -727,6 +727,7 @@ fn read_hyperlinks(e: &El, rels: &[Rel], sheet: &mut Sheet) {
let link = Hyperlink { target, tooltip: h.attr("tooltip").map(str::to_string) };
for c in range.iter() {
if cells >= MAX_HYPERLINK_CELLS {
cx.warn(format!("sheet \"{name}\": hyperlinks past the first {MAX_HYPERLINK_CELLS} cells were dropped"));
return;
}
cells += 1;
+16
View File
@@ -193,3 +193,19 @@ fn hostile_protected_ranges_stay_locked() {
assert!(!matches!(h.verify("password"), Ok(true)), "{}", r.title);
}
}
#[test]
fn hyperlinks_past_the_cell_cap_are_reported() {
// A styles part, so the only possible warning is the hyperlink one.
let parts = [("xl/styles.xml", r#"<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"/>"#)];
let rels = r#"<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>"#;
let body = r#"<sheetData/><hyperlinks><hyperlink ref="A1:A12000" location="x"/></hyperlinks>"#;
let (wb, rep) = read_xlsx(&minimal(body, &parts, rels, "")).unwrap();
assert_eq!(wb.sheet(0).unwrap().hyperlinks.len(), 10_000);
assert!(rep.warnings.iter().any(|w| w.contains("hyperlinks past the first 10000 cells were dropped")), "{:?}", rep.warnings);
// A small list reads in full with no warning.
let body = r#"<sheetData/><hyperlinks><hyperlink ref="A1:A3" location="x"/></hyperlinks>"#;
let (wb, rep) = read_xlsx(&minimal(body, &parts, rels, "")).unwrap();
assert_eq!(wb.sheet(0).unwrap().hyperlinks.len(), 3);
assert!(rep.warnings.is_empty(), "{:?}", rep.warnings);
}