From 3f8cebf1273dfb6e081f2c0db256063bcdda4555 Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Sat, 17 Oct 2026 21:01:24 +0300 Subject: [PATCH 1/2] gh-155877: Reject a NUL cell in curses.complexstr() from an iterable GH-156158 rejected a NUL in complexstr() given a str, but the iterable path packed each item without that check, so the example from the issue still built and addstr() dropped every cell after the NUL. --- Lib/test/test_curses.py | 2 ++ Modules/_cursesmodule.c | 34 +++++++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_curses.py b/Lib/test/test_curses.py index 5db44ebedd2ee0a..0eb43af795cdf80 100644 --- a/Lib/test/test_curses.py +++ b/Lib/test/test_curses.py @@ -1138,6 +1138,8 @@ def test_cell_null_char(self): # A string of cells cannot hold a NUL: it would end a batch write. self.assertRaises(ValueError, curses.complexstr, 'a\0b') self.assertRaises(ValueError, curses.complexstr, '\0') + self.assertRaises(ValueError, curses.complexstr, ['a', '\0', 'b']) + self.assertRaises(ValueError, curses.complexstr, [cell, 'b']) def test_add_string_behavior(self): # addstr() advances the cursor past the written text; addnstr() diff --git a/Modules/_cursesmodule.c b/Modules/_cursesmodule.c index 401b777be23caa6..a741b7754d0542e 100644 --- a/Modules/_cursesmodule.c +++ b/Modules/_cursesmodule.c @@ -1300,15 +1300,35 @@ curses_pack_cell(cursesmodule_state *state, PyObject *item, curses_cell_t *out) { if (Py_IS_TYPE(item, state->complexchar_type)) { *out = _PyCursesComplexCharObject_CAST(item)->cval; - return 0; } - if (PyUnicode_Check(item)) { - return curses_cell_pack(state, out, item, A_NORMAL, 0, "complexstr"); + else if (PyUnicode_Check(item)) { + if (curses_cell_pack(state, out, item, A_NORMAL, 0, "complexstr") < 0) { + return -1; + } } - PyErr_Format(PyExc_TypeError, - "complexstr cell must be a complexchar or a str, not %T", - item); - return -1; + else { + PyErr_Format(PyExc_TypeError, + "complexstr cell must be a complexchar or a str, not %T", + item); + return -1; + } +#ifdef HAVE_NCURSESW + wchar_t wstr[CCHARW_MAX + 1]; + attr_t attr; + int pair; + if (curses_getcchar(out, wstr, &attr, &pair) == ERR) { + PyErr_SetString(state->error, "getcchar() returned ERR"); + return -1; + } + int nul = wstr[0] == L'\0'; +#else + int nul = (*out & A_CHARTEXT) == 0; +#endif + if (nul) { + PyErr_SetString(PyExc_ValueError, "embedded null character"); + return -1; + } + return 0; } /* Wrap a buffer of len cells in a new complexstr, copying them in. tp_alloc From 4fc053424db0bfd7e76bebc4d70dad4ca129c216 Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Tue, 3 Nov 2026 17:54:01 +0200 Subject: [PATCH 2/2] Add a NEWS entry --- .../next/Library/2026-11-03-17-54-01.gh-issue-155877.KPDGNV.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-11-03-17-54-01.gh-issue-155877.KPDGNV.rst diff --git a/Misc/NEWS.d/next/Library/2026-11-03-17-54-01.gh-issue-155877.KPDGNV.rst b/Misc/NEWS.d/next/Library/2026-11-03-17-54-01.gh-issue-155877.KPDGNV.rst new file mode 100644 index 000000000000000..93fc4954e52d1d7 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-03-17-54-01.gh-issue-155877.KPDGNV.rst @@ -0,0 +1,2 @@ +:class:`curses.complexstr` now rejects a NUL item in an iterable, as it +already did for a NUL in a string.