Skip to content

Commit f6b1201

Browse files
[3.14] gh-154855: Ask non-ncurses curses for one more character (GH-154870) (GH-156284)
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() is left as it is. 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. (cherry picked from commit 43a1869)
1 parent 9b7eb47 commit f6b1201

2 files changed

Lines changed: 26 additions & 7 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :meth:`curses.window.instr` returning one character too few when the
2+
:mod:`curses` module is built against a curses library that counts the
3+
terminator in the requested length, such as the NetBSD one.

Modules/_cursesmodule.c

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,14 @@ static int curses_start_color_called = FALSE;
215215
while these functions are still in use. */
216216
static char *curses_screen_encoding = NULL;
217217

218+
/* ncurses and PDCurses store n characters and add a terminator; NetBSD
219+
curses counts the terminator in n. Ask an unknown library for one more. */
220+
#if defined(NCURSES_VERSION) || defined(PDCURSES)
221+
# define CURSES_STR_EXTRA 0
222+
#else
223+
# define CURSES_STR_EXTRA 1
224+
#endif
225+
218226
/* Utility Checking Procedures */
219227

220228
/*
@@ -2014,25 +2022,33 @@ PyCursesWindow_instr(PyObject *op, PyObject *args)
20142022
return NULL;
20152023
}
20162024

2017-
n = Py_MIN(n, max_buf_size - 1);
2025+
n = Py_MIN(n, max_buf_size - 1 - CURSES_STR_EXTRA);
2026+
n += CURSES_STR_EXTRA;
20182027
res = PyBytes_FromStringAndSize(NULL, n + 1);
20192028
if (res == NULL) {
20202029
return NULL;
20212030
}
20222031
char *buf = PyBytes_AS_STRING(res);
20232032

2024-
if (use_xy) {
2025-
rtn = mvwinnstr(self->win, y, x, buf, n);
2026-
}
2027-
else {
2028-
rtn = winnstr(self->win, buf, n);
2033+
/* Read again if the library stored more than asked: truncating could
2034+
split a multibyte character. */
2035+
for (unsigned int want = n - CURSES_STR_EXTRA; ; n = want) {
2036+
if (use_xy) {
2037+
rtn = mvwinnstr(self->win, y, x, buf, n);
2038+
}
2039+
else {
2040+
rtn = winnstr(self->win, buf, n);
2041+
}
2042+
if (rtn == ERR || (unsigned int)rtn <= want) {
2043+
break;
2044+
}
20292045
}
20302046

20312047
if (rtn == ERR) {
20322048
Py_DECREF(res);
20332049
return Py_GetConstant(Py_CONSTANT_EMPTY_BYTES);
20342050
}
2035-
_PyBytes_Resize(&res, strlen(buf)); // 'res' is set to NULL on failure
2051+
_PyBytes_Resize(&res, rtn); // 'res' is set to NULL on failure
20362052
return res;
20372053
}
20382054

0 commit comments

Comments
 (0)