Release govoplan-connectors v0.1.26: bound spreadsheet traversal

This commit is contained in:
2026-09-08 01:32:27 +02:00
parent 65609665a2
commit a54dae9536
6 changed files with 145 additions and 9 deletions
+70
View File
@@ -0,0 +1,70 @@
from __future__ import annotations
import re
import unittest
from io import BytesIO
from unittest.mock import patch
from zipfile import ZipFile
from openpyxl import Workbook
from govoplan_core.core.tabular_sources import TabularSourceValidationError
from govoplan_connectors.backend import tabular_adapters as adapters
def workbook_bytes(*, dimension: str | None = None, last_cell: str = "A2") -> bytes:
workbook = Workbook()
workbook.active.append(["name"])
workbook.active.append(["Ada"])
output = BytesIO()
workbook.save(output)
workbook.close()
result = BytesIO()
with ZipFile(BytesIO(output.getvalue())) as original, ZipFile(result, "w") as modified:
for entry in original.infolist():
content = original.read(entry)
if entry.filename == "xl/worksheets/sheet1.xml":
xml = content.decode()
if dimension is not None:
xml = re.sub(r'<dimension ref="[^"]+"\s*/>', f'<dimension ref="{dimension}"/>', xml)
xml = xml.replace('r="A2"', f'r="{last_cell}"')
xml = xml.replace('<row r="2">', f'<row r="{re.search(r"[0-9]+$", last_cell).group()}">')
content = xml.encode()
modified.writestr(entry, content)
return result.getvalue()
def parse(payload: bytes):
return adapters.parse_managed_tabular_content(payload, filename="fixture.xlsx", content_type=None, delimiter=",", sheet_name=None)
class XlsxSafetyBoundsTests(unittest.TestCase):
def test_sparse_rows_cannot_bypass_limit_by_not_counting_as_data(self):
with patch.object(adapters, "MAX_FILE_ROWS", 3):
with self.assertRaisesRegex(TabularSourceValidationError, "row"):
parse(workbook_bytes(last_cell="A6"))
def test_forged_small_dimensions_cannot_hide_far_away_cells(self):
with patch.object(adapters, "MAX_FILE_ROWS", 3):
with self.assertRaisesRegex(TabularSourceValidationError, "row"):
parse(workbook_bytes(dimension="A1:A2", last_cell="A1000000"))
def test_forged_small_dimensions_cannot_hide_out_of_range_columns(self):
with self.assertRaisesRegex(TabularSourceValidationError, "column"):
parse(workbook_bytes(dimension="A1:A2", last_cell="XFD2"))
def test_declared_dimensions_do_not_expand_or_truncate_real_rows(self):
for dimension in ("A1:XFD1048576", "A1:A1"):
with self.subTest(dimension=dimension):
rows, sheet = parse(workbook_bytes(dimension=dimension))
self.assertEqual(({"name": "Ada"},), rows)
self.assertEqual("Sheet", sheet)
def test_small_blank_gaps_and_exact_limit_remain_usable(self):
with patch.object(adapters, "MAX_FILE_ROWS", 3):
rows, _sheet = parse(workbook_bytes(last_cell="A4"))
self.assertEqual(({"name": "Ada"},), rows)
if __name__ == "__main__":
unittest.main()