Skip to content

Commit f7ad859

Browse files
authored
Merge pull request #215 from Maxteabag/fix-blob-markup-202
Render bytes as <BLOB N bytes> to avoid Rich markup crashes
2 parents e1c01d5 + 9e42e70 commit f7ad859

2 files changed

Lines changed: 68 additions & 1 deletion

File tree

sqlit/shared/ui/widgets_tables.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,11 @@ def _format_cell(self, obj: object, col: Any | None) -> Any:
100100
if isinstance(obj, timedelta):
101101
return str(obj)
102102

103+
if isinstance(obj, (bytes, bytearray, memoryview)):
104+
return f"<BLOB {len(bytes(obj))} bytes>"
105+
103106
if not is_renderable(obj):
104-
return str(obj)
107+
return escape(str(obj))
105108

106109
return obj
107110

tests/ui/test_blob_markup.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
"""Regression test for crash when rendering blobs containing markup-like text.
2+
3+
See https://github.com/.../issues/202: a PDF blob containing the substring
4+
``[/ICCBased 14 0 R]`` crashed the results table because Rich interpreted the
5+
brackets as a closing markup tag.
6+
"""
7+
8+
from __future__ import annotations
9+
10+
import io
11+
12+
import pytest
13+
from rich.console import Console
14+
15+
from sqlit.domains.shell.app.main import SSMSTUI
16+
from sqlit.shared.ui.widgets_tables import SqlitDataTable
17+
18+
from .mocks import MockConnectionStore, MockSettingsStore, build_test_services, create_test_connection
19+
20+
21+
# A snippet of real PDF stream contents that triggered the original crash.
22+
BLOB_WITH_MARKUP = b"%PDF-1.4\n[/ICCBased 14 0 R]\nstream..."
23+
24+
25+
def test_format_cell_bytes_does_not_crash_rich_console():
26+
"""Rendering the output of _format_cell on a bytes value must not raise MarkupError."""
27+
# _format_cell doesn't use self; call as unbound for a lightweight test.
28+
formatted = SqlitDataTable._format_cell(None, BLOB_WITH_MARKUP, None) # type: ignore[arg-type]
29+
30+
# Render through a Rich console the same way textual_fastdatatable does.
31+
console = Console(file=io.StringIO(), force_terminal=True, width=120)
32+
console.print(formatted) # Must not raise rich.errors.MarkupError.
33+
34+
35+
@pytest.mark.asyncio
36+
async def test_results_table_renders_blob_with_markup_chars():
37+
"""Displaying a row whose blob contains '[/...]' must not crash the table render."""
38+
connections = [create_test_connection("test-db", "sqlite")]
39+
services = build_test_services(
40+
connection_store=MockConnectionStore(connections),
41+
settings_store=MockSettingsStore({"theme": "tokyo-night"}),
42+
)
43+
app = SSMSTUI(services=services)
44+
45+
columns = ["id", "data"]
46+
rows = [(1, BLOB_WITH_MARKUP)]
47+
48+
async with app.run_test(size=(120, 40)) as pilot:
49+
await pilot.pause()
50+
51+
await app._display_query_results(
52+
columns=columns,
53+
rows=rows,
54+
row_count=len(rows),
55+
truncated=False,
56+
elapsed_ms=0,
57+
)
58+
59+
# Force the render loop to actually paint the cell — that's where the
60+
# original crash happened, not at insert time.
61+
for _ in range(3):
62+
await pilot.pause(0.05)
63+
64+
assert app.results_table.row_count == 1

0 commit comments

Comments
 (0)