diff --git a/crates/xlsx/src/ods.rs b/crates/xlsx/src/ods.rs
index 8415ce395..be9eb79bd 100644
--- a/crates/xlsx/src/ods.rs
+++ b/crates/xlsx/src/ods.rs
@@ -470,6 +470,14 @@ impl Import {
None => return Err(IoError::TooLarge("ODS row exceeds worksheet width".into())),
};
if kept {
+ // Charge the text a row keeps before the row is done, so a row of many long cells is
+ // refused as it grows; finish_row charges the row's repeats beyond the first.
+ if let Value::Text(t) = &cell.value {
+ self.text = self.text.saturating_add(u64::from(draft.repeat).saturating_mul(t.len() as u64));
+ if self.text > MAX_TEXT {
+ return Err(IoError::TooLarge("ODS expands beyond the import limit (64 MiB of text)".into()));
+ }
+ }
row.cells.push(RepeatedCell { col: row.col, repeat: draft.repeat, width, height, cell });
}
row.col = end;
@@ -537,7 +545,8 @@ impl Import {
if !entry.cell.is_blank() {
self.cells = self.cells.saturating_add(count);
if let Value::Text(t) = &entry.cell.value {
- self.text = self.text.saturating_add(count.saturating_mul(t.len() as u64));
+ let repeats = count.saturating_sub(u64::from(entry.repeat));
+ self.text = self.text.saturating_add(repeats.saturating_mul(t.len() as u64));
}
}
let merged = entry.width > 1 || entry.height > 1;
diff --git a/crates/xlsx/src/tests/ods_tests.rs b/crates/xlsx/src/tests/ods_tests.rs
index 47c258657..bac55efeb 100644
--- a/crates/xlsx/src/tests/ods_tests.rs
+++ b/crates/xlsx/src/tests/ods_tests.rs
@@ -200,6 +200,18 @@ fn ods_expansion_bounds_stop_small_repeat_bombs() {
"x".repeat(1024)
);
assert!(matches!(read_ods(&ods(&text)), Err(IoError::TooLarge(_))));
+ // One row of cells each just under the 1 MiB cell limit, past 64 MiB in all: refused while
+ // the row is read, before it holds them all. The row's end is never reached.
+ let long = format!(r#""#, "x".repeat(1024 * 1024 - 1));
+ let cells = ods(&format!(r#"{}"#, long.repeat(65)));
+ assert!(matches!(read_ods(&cells), Err(IoError::TooLarge(_))));
+ // A row repeated once more than its cells' text allows is refused too, and one that fits reads.
+ let cell = format!(r#""#, "y".repeat(1024));
+ let rows = |n: u32| {
+ ods(&format!(r#"{cell}"#))
+ };
+ assert!(read_ods(&rows(65_536)).is_ok());
+ assert!(matches!(read_ods(&rows(65_537)), Err(IoError::TooLarge(_))));
}
#[test]