Compare commits

...
Author SHA1 Message Date
Dylan M. Taylor 01dee46561 ods: charge a cell's text when the cell is read, not when its row ends
The importer kept every cell of a row and only charged their text to
the 64 MiB allowance when the row finished, so one row of long cells
could hold up to 16,384 MiB before being refused. Text is now charged
as each cell finishes; the row charges only its repeats.
2026-11-03 11:15:58 +00:00
2 changed files with 22 additions and 1 deletions
+10 -1
View File
@@ -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;
+12
View File
@@ -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#"<table:table-cell office:value-type="string" office:string-value="{}"/>"#, "x".repeat(1024 * 1024 - 1));
let cells = ods(&format!(r#"<table:table table:name="Data"><table:table-row>{}"#, 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#"<table:table-cell office:value-type="string" office:string-value="{}"/>"#, "y".repeat(1024));
let rows = |n: u32| {
ods(&format!(r#"<table:table table:name="Data"><table:table-row table:number-rows-repeated="{n}">{cell}</table:table-row></table:table>"#))
};
assert!(read_ods(&rows(65_536)).is_ok());
assert!(matches!(read_ods(&rows(65_537)), Err(IoError::TooLarge(_))));
}
#[test]