H1+M2 review findings: CSV 5000x50 render cap (truncated with note) + stale output sweep; 4 regression tests (H1x3, M2x1); USER_GUIDE truncation docs
Test / Tests (Python 3.12) (push) Canceled after 0s
Test / Tests (Python 3.12) (push) Canceled after 0s
This commit is contained in:
@@ -18,6 +18,20 @@ from . import common
|
||||
from .text_converter import decode_bytes
|
||||
|
||||
|
||||
# H1 render cap: a single oversized CSV must not exhaust the single worker
|
||||
# thread (unbounded CPU/memory DoS). Rows/columns are TRUNCATED (not rejected),
|
||||
# and a visible note is appended so the behaviour is honest. Mirrors the
|
||||
# documented limit (USER_GUIDE.md: max 5000 rows x 50 cols).
|
||||
MAX_ROWS = 5000
|
||||
MAX_COLS = 50
|
||||
|
||||
# Visible note appended to the rendered table when the cap is exceeded.
|
||||
_TRUNCATION_NOTE = (
|
||||
"<p class=\"warn\">truncated to 5000 rows x 50 cols; "
|
||||
"split the file to include more</p>"
|
||||
)
|
||||
|
||||
|
||||
def _rows(text: str, delimiter: str) -> list[list[str]]:
|
||||
rows = list(csv.reader(io.StringIO(text), delimiter=delimiter))
|
||||
return [row for row in rows if any(cell.strip() for cell in row)]
|
||||
@@ -26,13 +40,19 @@ def _rows(text: str, delimiter: str) -> list[list[str]]:
|
||||
def _table_html(rows: list[list[str]]) -> str:
|
||||
if not rows:
|
||||
return ""
|
||||
header, body = rows[0], rows[1:]
|
||||
head = "".join(f"<th>{common.escape(c)}</th>" for c in header)
|
||||
# H1: cap to the first MAX_ROWS rows and first MAX_COLS columns.
|
||||
shown = rows[:MAX_ROWS]
|
||||
header, body = shown[0], shown[1:]
|
||||
n_cols = min(len(header), MAX_COLS)
|
||||
head = "".join(f"<th>{common.escape(c)}</th>" for c in header[:MAX_COLS])
|
||||
out = [f"<table><tr>{head}</tr>"]
|
||||
truncated = len(rows) > MAX_ROWS
|
||||
for row in body:
|
||||
# pad ragged rows to header width so borders line up
|
||||
cells = row + [""] * (len(header) - len(row))
|
||||
# pad ragged rows to the (truncated) header width so borders line up
|
||||
cells = row[:MAX_COLS] + [""] * (n_cols - len(row[:MAX_COLS]))
|
||||
out.append("<tr>" + "".join(f"<td>{common.escape(c)}</td>" for c in cells) + "</tr>")
|
||||
if truncated:
|
||||
out.append(_TRUNCATION_NOTE)
|
||||
out.append("</table>")
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user