Skip to content

Commit 0777f50

Browse files
gh-154855: Ask non-ncurses curses for one more character
Passing n to the library is ncurses' reading of n: it stores n characters and adds a terminator. NetBSD curses counts the terminator in n. Ask a library that is neither ncurses nor PDCurses for n + 1, and read again if it stored more than asked; truncating could split a multibyte character. This is not possible for input, so getstr() and get_wstr() are left as they are. instr() now takes the length from the value returned by winnstr(), as X/Open specifies, instead of searching for a terminator which it does not.
1 parent 9c91fd9 commit 0777f50

1 file changed

Lines changed: 41 additions & 14 deletions

File tree

Modules/_cursesmodule.c

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,14 @@ curses_window_set_null_error(PyCursesWindowObject *win,
309309
_curses_set_null_error(state, curses_funcname, python_funcname);
310310
}
311311

312+
/* ncurses and PDCurses store n characters and add a terminator; NetBSD
313+
curses counts the terminator in n. Ask an unknown library for one more. */
314+
#if defined(NCURSES_VERSION) || defined(PDCURSES)
315+
# define CURSES_STR_EXTRA 0
316+
#else
317+
# define CURSES_STR_EXTRA 1
318+
#endif
319+
312320
/* Utility Checking Procedures */
313321

314322
/*
@@ -3796,25 +3804,33 @@ curses_window_instr_bytes(PyCursesWindowObject *self, PyObject *args,
37963804
return NULL;
37973805
}
37983806

3799-
n = Py_MIN(n, max_buf_size - 1);
3807+
n = Py_MIN(n, max_buf_size - 1 - CURSES_STR_EXTRA);
3808+
n += CURSES_STR_EXTRA;
38003809
PyBytesWriter *writer = PyBytesWriter_Create(n + 1);
38013810
if (writer == NULL) {
38023811
return NULL;
38033812
}
38043813
char *buf = PyBytesWriter_GetData(writer);
38053814

3806-
if (use_xy) {
3807-
rtn = mvwinnstr(self->win, y, x, buf, n);
3808-
}
3809-
else {
3810-
rtn = winnstr(self->win, buf, n);
3815+
/* Read again if the library stored more than asked: truncating could
3816+
split a multibyte character. */
3817+
for (unsigned int want = n - CURSES_STR_EXTRA; ; n = want) {
3818+
if (use_xy) {
3819+
rtn = mvwinnstr(self->win, y, x, buf, n);
3820+
}
3821+
else {
3822+
rtn = winnstr(self->win, buf, n);
3823+
}
3824+
if (rtn == ERR || (unsigned int)rtn <= want) {
3825+
break;
3826+
}
38113827
}
38123828

38133829
if (rtn == ERR) {
38143830
PyBytesWriter_Discard(writer);
38153831
return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
38163832
}
3817-
return PyBytesWriter_FinishWithSize(writer, strlen(buf));
3833+
return PyBytesWriter_FinishWithSize(writer, rtn);
38183834
}
38193835

38203836
static PyObject *
@@ -3939,17 +3955,25 @@ PyCursesWindow_in_wstr(PyObject *op, PyObject *args)
39393955
return NULL;
39403956
}
39413957

3942-
n = Py_MIN(n, max_buf_size - 1);
3958+
n = Py_MIN(n, max_buf_size - 1 - CURSES_STR_EXTRA);
3959+
n += CURSES_STR_EXTRA;
39433960
wchar_t *buf = PyMem_New(wchar_t, n + 1);
39443961
if (buf == NULL) {
39453962
return PyErr_NoMemory();
39463963
}
39473964

3948-
if (use_xy) {
3949-
rtn = mvwinnwstr(self->win, y, x, buf, n);
3950-
}
3951-
else {
3952-
rtn = winnwstr(self->win, buf, n);
3965+
/* Read again if the library stored more than asked: truncating could
3966+
separate a combining character from its base. */
3967+
for (unsigned int want = n - CURSES_STR_EXTRA; ; n = want) {
3968+
if (use_xy) {
3969+
rtn = mvwinnwstr(self->win, y, x, buf, n);
3970+
}
3971+
else {
3972+
rtn = winnwstr(self->win, buf, n);
3973+
}
3974+
if (rtn == ERR || (unsigned int)rtn <= want) {
3975+
break;
3976+
}
39533977
}
39543978

39553979
if (rtn == ERR) {
@@ -4003,7 +4027,8 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
40034027
return NULL;
40044028
}
40054029

4006-
n = Py_MIN(n, max_buf_size - 1);
4030+
n = Py_MIN(n, max_buf_size - 1 - CURSES_STR_EXTRA);
4031+
n += CURSES_STR_EXTRA;
40074032
cursesmodule_state *state = get_cursesmodule_state_by_win(self);
40084033
/* Zero the cells: reading a cell back through getcchar() relies on the
40094034
cchar_t text array being NUL-terminated, which some curses libraries
@@ -4026,6 +4051,7 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
40264051
return PyCursesComplexStr_New(state, NULL, 0);
40274052
}
40284053

4054+
n -= CURSES_STR_EXTRA;
40294055
/* win_wchnstr() stores at most n cells and zero-terminates the array at
40304056
the actual count; every real cell holds at least a space, so the first
40314057
empty cell marks the end of the run. */
@@ -4058,6 +4084,7 @@ PyCursesWindow_in_wchstr(PyObject *op, PyObject *args)
40584084
return PyCursesComplexStr_New(state, NULL, 0);
40594085
}
40604086

4087+
n -= CURSES_STR_EXTRA;
40614088
Py_ssize_t count = 0;
40624089
while (count < (Py_ssize_t)n && buf[count] != 0) {
40634090
count++;

0 commit comments

Comments
 (0)