@@ -4,6 +4,10 @@ use std::io::Read;
44
55use crate :: config:: ConvertOptions ;
66use crate :: error:: { ConvertError , ConvertWarning } ;
7+
8+ /// Maximum nesting depth for tables-within-tables. Deeper nesting is silently
9+ /// truncated to prevent stack overflow on pathological documents.
10+ const MAX_TABLE_DEPTH : usize = 64 ;
711use crate :: ir:: {
812 Alignment , Block , BorderLineStyle , BorderSide , CellBorder , Chart , Color , ColumnLayout ,
913 Document , FloatingImage , FlowPage , HFInline , HeaderFooter , HeaderFooterParagraph , ImageData ,
@@ -1013,6 +1017,7 @@ impl Parser for DocxParser {
10131017 & notes,
10141018 & wraps,
10151019 & bidi,
1020+ 0 ,
10161021 ) ) ] ) ]
10171022 }
10181023 docx_rs:: DocumentChild :: StructuredDataTag ( sdt) => convert_sdt_children (
@@ -1268,7 +1273,7 @@ fn convert_sdt_children(
12681273 }
12691274 docx_rs:: StructuredDataTagChild :: Table ( table) => {
12701275 result. push ( TaggedElement :: Plain ( vec ! [ Block :: Table ( convert_table(
1271- table, images, hyperlinks, style_map, notes, wraps, bidi,
1276+ table, images, hyperlinks, style_map, notes, wraps, bidi, 0 ,
12721277 ) ) ] ) ) ;
12731278 }
12741279 docx_rs:: StructuredDataTagChild :: StructuredDataTag ( nested) => {
@@ -1631,6 +1636,7 @@ fn extract_line_spacing(
16311636 ( line_spacing, space_before, space_after)
16321637}
16331638
1639+ #[ allow( clippy:: too_many_arguments) ]
16341640/// Convert a docx-rs Table to an IR Table.
16351641///
16361642/// Handles:
@@ -1648,11 +1654,14 @@ fn convert_table(
16481654 notes : & NoteContext ,
16491655 wraps : & WrapContext ,
16501656 bidi : & BidiContext ,
1657+ depth : usize ,
16511658) -> Table {
16521659 let column_widths: Vec < f64 > = table. grid . iter ( ) . map ( |& w| w as f64 / 20.0 ) . collect ( ) ;
16531660
16541661 // First pass: extract raw rows with vmerge info for rowspan calculation
1655- let raw_rows = extract_raw_rows ( table, images, hyperlinks, style_map, notes, wraps, bidi) ;
1662+ let raw_rows = extract_raw_rows (
1663+ table, images, hyperlinks, style_map, notes, wraps, bidi, depth,
1664+ ) ;
16561665
16571666 // Second pass: resolve vertical merges into rowspan values and build IR rows
16581667 let rows = resolve_vmerge_and_build_rows ( & raw_rows) ;
@@ -1673,6 +1682,7 @@ struct RawCell {
16731682 background : Option < Color > ,
16741683}
16751684
1685+ #[ allow( clippy:: too_many_arguments) ]
16761686/// Extract raw rows from a docx-rs Table, tracking column indices and vmerge state.
16771687fn extract_raw_rows (
16781688 table : & docx_rs:: Table ,
@@ -1682,6 +1692,7 @@ fn extract_raw_rows(
16821692 notes : & NoteContext ,
16831693 wraps : & WrapContext ,
16841694 bidi : & BidiContext ,
1695+ depth : usize ,
16851696) -> Vec < Vec < RawCell > > {
16861697 let mut raw_rows = Vec :: new ( ) ;
16871698
@@ -1706,8 +1717,9 @@ fn extract_raw_rows(
17061717 . and_then ( |v| v. as_str ( ) )
17071718 . map ( String :: from) ;
17081719
1709- let content =
1710- extract_cell_content ( cell, images, hyperlinks, style_map, notes, wraps, bidi) ;
1720+ let content = extract_cell_content (
1721+ cell, images, hyperlinks, style_map, notes, wraps, bidi, depth,
1722+ ) ;
17111723 let border = prop_json
17121724 . as_ref ( )
17131725 . and_then ( |j| j. get ( "borders" ) )
@@ -1802,6 +1814,7 @@ fn count_vmerge_span(raw_rows: &[Vec<RawCell>], start_row: usize, col_index: usi
18021814 span
18031815}
18041816
1817+ #[ allow( clippy:: too_many_arguments) ]
18051818/// Extract cell content (paragraphs) from a docx-rs TableCell.
18061819fn extract_cell_content (
18071820 cell : & docx_rs:: TableCell ,
@@ -1811,6 +1824,7 @@ fn extract_cell_content(
18111824 notes : & NoteContext ,
18121825 wraps : & WrapContext ,
18131826 bidi : & BidiContext ,
1827+ depth : usize ,
18141828) -> Vec < Block > {
18151829 let mut blocks = Vec :: new ( ) ;
18161830 for content in & cell. children {
@@ -1828,15 +1842,19 @@ fn extract_cell_content(
18281842 ) ;
18291843 }
18301844 docx_rs:: TableCellContent :: Table ( nested_table) => {
1831- blocks. push ( Block :: Table ( convert_table (
1832- nested_table,
1833- images,
1834- hyperlinks,
1835- style_map,
1836- notes,
1837- wraps,
1838- bidi,
1839- ) ) ) ;
1845+ if depth < MAX_TABLE_DEPTH {
1846+ blocks. push ( Block :: Table ( convert_table (
1847+ nested_table,
1848+ images,
1849+ hyperlinks,
1850+ style_map,
1851+ notes,
1852+ wraps,
1853+ bidi,
1854+ depth + 1 ,
1855+ ) ) ) ;
1856+ }
1857+ // Silently skip nested tables beyond MAX_TABLE_DEPTH
18401858 }
18411859 _ => { }
18421860 }
0 commit comments