fix curses.newwin() crash on narrow terminals in TUI dialogs
show_confirm() sized its window purely from message content, with no upper bound — a long line (the Cryptgeon password URL in the post-issuance "send email?" prompt) could easily exceed a narrow terminal's width (e.g. a serial console), making curses.newwin() raise "curses function returned NULL" and crash the whole app. show_cert_form() had the same unguarded newwin() call, just with a fixed 13x62 size instead of content-driven. Clamp both windows' height/width to the actual screen size, and wrap newwin() itself in try/except as a last-resort fallback (declined confirm / cancelled form) in case clamping still isn't enough. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,31 @@ def test_show_confirm_esc():
|
||||
assert result is False
|
||||
|
||||
|
||||
def test_show_confirm_clamps_width_to_screen_for_long_line():
|
||||
"""A long message line (e.g. a Cryptgeon password URL) must not make the
|
||||
requested newwin() width exceed the screen — that's what made
|
||||
curses.newwin() raise 'curses function returned NULL' on a narrow
|
||||
terminal such as a serial console."""
|
||||
stdscr = _make_stdscr(rows=24, cols=80)
|
||||
win = _make_win()
|
||||
win.getch.side_effect = [ord("y")]
|
||||
long_url = "https://cryptgeon.example.com/note/" + "a" * 100 + "#" + "b" * 64
|
||||
with patch("curses.newwin", return_value=win) as mock_newwin:
|
||||
show_confirm(stdscr, f"Send email?\n\nPassword URL: {long_url}")
|
||||
h, w, y, x = mock_newwin.call_args.args
|
||||
assert w <= 80
|
||||
assert x >= 0
|
||||
|
||||
|
||||
def test_show_confirm_newwin_failure_returns_false():
|
||||
"""If curses.newwin() still fails despite clamping, show_confirm must
|
||||
degrade to 'declined' rather than crash the whole app."""
|
||||
stdscr = _make_stdscr()
|
||||
with patch("curses.newwin", side_effect=_curses.error("returned NULL")):
|
||||
result = show_confirm(stdscr, "Do you want to continue?")
|
||||
assert result is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# show_cert_form tests
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -102,3 +127,28 @@ def test_show_cert_form_cancel_button():
|
||||
result = show_cert_form(stdscr, cn="alice", email="alice@example.com")
|
||||
assert isinstance(result, CertFormResult)
|
||||
assert result.confirmed is False
|
||||
|
||||
|
||||
def test_show_cert_form_clamps_to_narrow_screen():
|
||||
"""The form's fixed 13x62 size must not exceed a smaller-than-usual
|
||||
screen, which would make curses.newwin() raise."""
|
||||
stdscr = _make_stdscr(rows=10, cols=40)
|
||||
win = _make_win()
|
||||
win.getch.side_effect = [27]
|
||||
with patch("curses.newwin", return_value=win) as mock_newwin:
|
||||
show_cert_form(stdscr, cn="alice", email="alice@example.com")
|
||||
h, w, y, x = mock_newwin.call_args.args
|
||||
assert h <= 10
|
||||
assert w <= 40
|
||||
assert y >= 0
|
||||
assert x >= 0
|
||||
|
||||
|
||||
def test_show_cert_form_newwin_failure_returns_cancelled():
|
||||
"""If curses.newwin() still fails despite clamping, show_cert_form must
|
||||
degrade to an unconfirmed result rather than crash the whole app."""
|
||||
stdscr = _make_stdscr()
|
||||
with patch("curses.newwin", side_effect=_curses.error("returned NULL")):
|
||||
result = show_cert_form(stdscr, cn="alice", email="alice@example.com")
|
||||
assert isinstance(result, CertFormResult)
|
||||
assert result.confirmed is False
|
||||
|
||||
Reference in New Issue
Block a user