Skip to main content

comrak/parser/
table.rs

1use crate::arena_tree::Node;
2use crate::nodes;
3use crate::nodes::{Ast, AstNode, NodeTable, NodeValue, TableAlignment};
4use crate::parser::Parser;
5use crate::scanners;
6use crate::strings::trim;
7use std::cell::RefCell;
8use std::cmp::min;
9
10use super::inlines::count_newlines;
11
12// Limit to prevent a malicious input from causing a denial of service.
13const MAX_AUTOCOMPLETED_CELLS: usize = 500_000;
14
15pub fn try_opening_block<'a>(
16    parser: &mut Parser<'a, '_, '_>,
17    container: &'a AstNode<'a>,
18    line: &[u8],
19) -> Option<(&'a AstNode<'a>, bool, bool)> {
20    let aligns = match container.data.borrow().value {
21        NodeValue::Paragraph => None,
22        NodeValue::Table(NodeTable { ref alignments, .. }) => Some(alignments.clone()),
23        _ => return None,
24    };
25
26    match aligns {
27        None => try_opening_header(parser, container, line),
28        Some(ref aligns) => try_opening_row(parser, container, aligns, line),
29    }
30}
31
32fn try_opening_header<'a>(
33    parser: &mut Parser<'a, '_, '_>,
34    container: &'a AstNode<'a>,
35    line: &[u8],
36) -> Option<(&'a AstNode<'a>, bool, bool)> {
37    if container.data.borrow().table_visited {
38        return Some((container, false, false));
39    }
40
41    if scanners::table_start(&line[parser.first_nonspace..]).is_none() {
42        return Some((container, false, false));
43    }
44
45    let spoiler = parser.options.extension.spoiler;
46
47    let delimiter_row = match row(&line[parser.first_nonspace..], spoiler) {
48        Some(delimiter_row) => delimiter_row,
49        None => return Some((container, false, true)),
50    };
51
52    let header_row = match row(container.data.borrow().content.as_bytes(), spoiler) {
53        Some(header_row) => header_row,
54        None => return Some((container, false, true)),
55    };
56
57    if header_row.cells.len() != delimiter_row.cells.len() {
58        return Some((container, false, true));
59    }
60
61    if header_row.paragraph_offset > 0 {
62        try_inserting_table_header_paragraph(parser, container, header_row.paragraph_offset);
63    }
64
65    let mut alignments = vec![];
66    for cell in delimiter_row.cells {
67        let cell_content = cell.content.as_bytes();
68        let left = !cell_content.is_empty() && cell_content[0] == b':';
69        let right = !cell_content.is_empty() && cell_content[cell_content.len() - 1] == b':';
70        alignments.push(if left && right {
71            TableAlignment::Center
72        } else if left {
73            TableAlignment::Left
74        } else if right {
75            TableAlignment::Right
76        } else {
77            TableAlignment::None
78        });
79    }
80
81    let start = container.data.borrow().sourcepos.start;
82    let child = Ast::new(
83        NodeValue::Table(NodeTable {
84            alignments,
85            num_columns: header_row.cells.len(),
86            num_rows: 0,
87            num_nonempty_cells: 0,
88        }),
89        start,
90    );
91    let table = parser.arena.alloc(Node::new(RefCell::new(child)));
92    container.append(table);
93
94    let header = parser.add_child(table, NodeValue::TableRow(true), start.column);
95    {
96        let header_ast = &mut header.data.borrow_mut();
97        header_ast.sourcepos.start.line = start.line;
98        header_ast.sourcepos.end = start.column_add(
99            (container.data.borrow().content.as_bytes().len() - 2 - header_row.paragraph_offset)
100                as isize,
101        );
102    }
103
104    let mut i = 0;
105
106    while i < header_row.cells.len() {
107        let cell = &header_row.cells[i];
108        let ast_cell = parser.add_child(
109            header,
110            NodeValue::TableCell,
111            start.column + cell.start_offset - header_row.paragraph_offset,
112        );
113        let ast = &mut ast_cell.data.borrow_mut();
114        ast.sourcepos.start.line = start.line;
115        ast.sourcepos.end =
116            start.column_add((cell.end_offset - header_row.paragraph_offset) as isize);
117        ast.internal_offset = cell.internal_offset;
118        ast.content.clone_from(&cell.content);
119        ast.line_offsets.push(
120            start.column + cell.start_offset - 1 + cell.internal_offset
121                - header_row.paragraph_offset,
122        );
123
124        i += 1;
125    }
126
127    incr_table_row_count(container, i);
128
129    let offset = line.len() - 1 - parser.offset;
130    parser.advance_offset(line, offset, false);
131
132    Some((table, true, false))
133}
134
135fn try_opening_row<'a>(
136    parser: &mut Parser<'a, '_, '_>,
137    container: &'a AstNode<'a>,
138    alignments: &[TableAlignment],
139    line: &[u8],
140) -> Option<(&'a AstNode<'a>, bool, bool)> {
141    if parser.blank {
142        return None;
143    }
144
145    if get_num_autocompleted_cells(container) > MAX_AUTOCOMPLETED_CELLS {
146        return None;
147    }
148
149    let sourcepos = container.data.borrow().sourcepos;
150    let spoiler = parser.options.extension.spoiler;
151    let this_row = match row(&line[parser.first_nonspace..], spoiler) {
152        Some(this_row) => this_row,
153        None => return None,
154    };
155
156    let new_row = parser.add_child(
157        container,
158        NodeValue::TableRow(false),
159        sourcepos.start.column,
160    );
161    {
162        new_row.data.borrow_mut().sourcepos.end.column = sourcepos.end.column;
163    }
164
165    let mut i = 0;
166    let mut last_column = sourcepos.start.column;
167
168    while i < min(alignments.len(), this_row.cells.len()) {
169        let cell = &this_row.cells[i];
170        let cell_node = parser.add_child(
171            new_row,
172            NodeValue::TableCell,
173            sourcepos.start.column + cell.start_offset,
174        );
175        let cell_ast = &mut cell_node.data.borrow_mut();
176        cell_ast.internal_offset = cell.internal_offset;
177        cell_ast.sourcepos.end.column = sourcepos.start.column + cell.end_offset;
178        cell_ast.content.clone_from(&cell.content);
179        cell_ast
180            .line_offsets
181            .push(sourcepos.start.column + cell.start_offset - 1 + cell.internal_offset);
182
183        last_column = cell_ast.sourcepos.end.column;
184
185        i += 1;
186    }
187
188    incr_table_row_count(container, i);
189
190    while i < alignments.len() {
191        parser.add_child(new_row, NodeValue::TableCell, last_column);
192        i += 1;
193    }
194
195    let offset = line.len() - 1 - parser.offset;
196    parser.advance_offset(line, offset, false);
197
198    Some((new_row, false, false))
199}
200
201struct Row {
202    paragraph_offset: usize,
203    cells: Vec<Cell>,
204}
205
206struct Cell {
207    start_offset: usize,
208    end_offset: usize,
209    internal_offset: usize,
210    content: String,
211}
212
213fn row(string: &[u8], spoiler: bool) -> Option<Row> {
214    let len = string.len();
215    let mut cells: Vec<Cell> = vec![];
216
217    let mut offset = scanners::table_cell_end(string).unwrap_or(0);
218
219    let mut paragraph_offset = 0;
220    let mut expect_more_cells = true;
221    let mut max_columns_abort = false;
222
223    while offset < len && expect_more_cells {
224        let cell_matched = scanners::table_cell(&string[offset..], spoiler).unwrap_or(0);
225        let pipe_matched = scanners::table_cell_end(&string[offset + cell_matched..]).unwrap_or(0);
226
227        if cell_matched > 0 || pipe_matched > 0 {
228            let mut cell = unescape_pipes(&string[offset..offset + cell_matched]);
229            trim(&mut cell);
230
231            let mut start_offset = offset;
232            let mut internal_offset = 0;
233
234            while start_offset > paragraph_offset && string[start_offset - 1] != b'|' {
235                start_offset -= 1;
236                internal_offset += 1;
237            }
238
239            // set an upper limit on the number of columns
240            if cells.len() == <u16 as Into<usize>>::into(u16::MAX) {
241                max_columns_abort = true;
242                break;
243            }
244
245            cells.push(Cell {
246                start_offset,
247                end_offset: offset + cell_matched - 1,
248                internal_offset,
249                content: String::from_utf8(cell).unwrap(),
250            });
251        }
252
253        offset += cell_matched + pipe_matched;
254
255        if pipe_matched > 0 {
256            expect_more_cells = true;
257        } else {
258            let row_end_offset = scanners::table_row_end(&string[offset..]).unwrap_or(0);
259            offset += row_end_offset;
260
261            if row_end_offset > 0 && offset != len {
262                paragraph_offset = offset;
263                cells.clear();
264                offset += scanners::table_cell_end(&string[offset..]).unwrap_or(0);
265                expect_more_cells = true;
266            } else {
267                expect_more_cells = false;
268            }
269        }
270    }
271
272    if offset != len || cells.is_empty() || max_columns_abort {
273        None
274    } else {
275        Some(Row {
276            paragraph_offset,
277            cells,
278        })
279    }
280}
281
282fn try_inserting_table_header_paragraph<'a>(
283    parser: &mut Parser<'a, '_, '_>,
284    container: &'a AstNode<'a>,
285    paragraph_offset: usize,
286) {
287    let container_ast = &mut container.data.borrow_mut();
288
289    let preface = &container_ast.content.as_bytes()[..paragraph_offset];
290    let mut paragraph_content = unescape_pipes(preface);
291    let (newlines, _since_newline) = count_newlines(&paragraph_content);
292    trim(&mut paragraph_content);
293
294    if container.parent().is_none()
295        || !nodes::can_contain_type(container.parent().unwrap(), &NodeValue::Paragraph)
296    {
297        return;
298    }
299
300    let start = container_ast.sourcepos.start;
301
302    let mut paragraph = Ast::new(NodeValue::Paragraph, start);
303    paragraph.sourcepos.end.line = start.line + newlines - 1;
304
305    // copy over the line offsets related to the paragraph
306    for n in 0..newlines {
307        paragraph.line_offsets.push(container_ast.line_offsets[n]);
308    }
309
310    let last_line_offset = *paragraph.line_offsets.last().unwrap_or(&0);
311    paragraph.sourcepos.end.column = last_line_offset
312        + preface
313            .iter()
314            .rev()
315            .skip(1)
316            .take_while(|&&c| c != b'\n')
317            .count();
318
319    container_ast.sourcepos.start.line += newlines;
320
321    paragraph.content = String::from_utf8(paragraph_content).unwrap();
322    let node = parser.arena.alloc(Node::new(RefCell::new(paragraph)));
323    container.insert_before(node);
324}
325
326fn unescape_pipes(string: &[u8]) -> Vec<u8> {
327    let len = string.len();
328    let mut v = Vec::with_capacity(len);
329
330    for (i, &c) in string.iter().enumerate() {
331        if c == b'\\' && i + 1 < len && string[i + 1] == b'|' {
332            continue;
333        } else {
334            v.push(c);
335        }
336    }
337
338    v
339}
340
341// Increment the number of rows in the table. Also update n_nonempty_cells,
342// which keeps track of the number of cells which were parsed from the
343// input file. (If one of the rows is too short, then the trailing cells
344// are autocompleted. Autocompleted cells are not counted in n_nonempty_cells.)
345// The purpose of this is to prevent a malicious input from generating a very
346// large number of autocompleted cells, which could cause a denial of service
347// vulnerability.
348fn incr_table_row_count<'a>(container: &'a AstNode<'a>, i: usize) -> bool {
349    return match container.data.borrow_mut().value {
350        NodeValue::Table(ref mut node_table) => {
351            node_table.num_rows += 1;
352            node_table.num_nonempty_cells += i;
353            true
354        }
355        _ => false,
356    };
357}
358
359// Calculate the number of autocompleted cells.
360fn get_num_autocompleted_cells<'a>(container: &'a AstNode<'a>) -> usize {
361    return match container.data.borrow().value {
362        NodeValue::Table(ref node_table) => {
363            let num_cells = node_table.num_columns * node_table.num_rows;
364
365            if num_cells < node_table.num_nonempty_cells {
366                0
367            } else {
368                (node_table.num_columns * node_table.num_rows) - node_table.num_nonempty_cells
369            }
370        }
371        _ => 0,
372    };
373}
374
375pub fn matches(line: &[u8], spoiler: bool) -> bool {
376    row(line, spoiler).is_some()
377}