Skip to content

Commit 4ba15de

Browse files
gh-74616: Raise ValueError in case of null character in input prompt (GH-1738)
If the input prompt to the builtin input function on terminal has any null character, then raise ValueError instead of silently truncating it. Co-authored-by: Serhiy Storchaka <[email protected]>
1 parent 8660fb7 commit 4ba15de

File tree

3 files changed

+42
-9
lines changed

3 files changed

+42
-9
lines changed

Lib/test/test_builtin.py

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2269,30 +2269,37 @@ def _run_child(self, child, terminal_input):
22692269

22702270
return lines
22712271

2272-
def check_input_tty(self, prompt, terminal_input, stdio_encoding=None):
2272+
def check_input_tty(self, prompt, terminal_input, stdio_encoding=None, *,
2273+
expected=None,
2274+
stdin_errors='surrogateescape',
2275+
stdout_errors='replace'):
22732276
if not sys.stdin.isatty() or not sys.stdout.isatty():
22742277
self.skipTest("stdin and stdout must be ttys")
22752278
def child(wpipe):
22762279
# Check the error handlers are accounted for
22772280
if stdio_encoding:
22782281
sys.stdin = io.TextIOWrapper(sys.stdin.detach(),
22792282
encoding=stdio_encoding,
2280-
errors='surrogateescape')
2283+
errors=stdin_errors)
22812284
sys.stdout = io.TextIOWrapper(sys.stdout.detach(),
22822285
encoding=stdio_encoding,
2283-
errors='replace')
2286+
errors=stdout_errors)
22842287
print("tty =", sys.stdin.isatty() and sys.stdout.isatty(), file=wpipe)
2285-
print(ascii(input(prompt)), file=wpipe)
2288+
try:
2289+
print(ascii(input(prompt)), file=wpipe)
2290+
except BaseException as e:
2291+
print(ascii(f'{e.__class__.__name__}: {e!s}'), file=wpipe)
22862292
lines = self.run_child(child, terminal_input + b"\r\n")
22872293
# Check we did exercise the GNU readline path
22882294
self.assertIn(lines[0], {'tty = True', 'tty = False'})
22892295
if lines[0] != 'tty = True':
22902296
self.skipTest("standard IO in should have been a tty")
22912297
input_result = eval(lines[1]) # ascii() -> eval() roundtrip
2292-
if stdio_encoding:
2293-
expected = terminal_input.decode(stdio_encoding, 'surrogateescape')
2294-
else:
2295-
expected = terminal_input.decode(sys.stdin.encoding) # what else?
2298+
if expected is None:
2299+
if stdio_encoding:
2300+
expected = terminal_input.decode(stdio_encoding, 'surrogateescape')
2301+
else:
2302+
expected = terminal_input.decode(sys.stdin.encoding) # what else?
22962303
self.assertEqual(input_result, expected)
22972304

22982305
def test_input_tty(self):
@@ -2313,13 +2320,32 @@ def skip_if_readline(self):
23132320
def test_input_tty_non_ascii(self):
23142321
self.skip_if_readline()
23152322
# Check stdin/stdout encoding is used when invoking PyOS_Readline()
2316-
self.check_input_tty("prompté", b"quux\xe9", "utf-8")
2323+
self.check_input_tty("prompté", b"quux\xc3\xa9", "utf-8")
23172324

23182325
def test_input_tty_non_ascii_unicode_errors(self):
23192326
self.skip_if_readline()
23202327
# Check stdin/stdout error handler is used when invoking PyOS_Readline()
23212328
self.check_input_tty("prompté", b"quux\xe9", "ascii")
23222329

2330+
def test_input_tty_null_in_prompt(self):
2331+
self.check_input_tty("prompt\0", b"",
2332+
expected='ValueError: input: prompt string cannot contain '
2333+
'null characters')
2334+
2335+
def test_input_tty_nonencodable_prompt(self):
2336+
self.skip_if_readline()
2337+
self.check_input_tty("prompté", b"quux", "ascii", stdout_errors='strict',
2338+
expected="UnicodeEncodeError: 'ascii' codec can't encode "
2339+
"character '\\xe9' in position 6: ordinal not in "
2340+
"range(128)")
2341+
2342+
def test_input_tty_nondecodable_input(self):
2343+
self.skip_if_readline()
2344+
self.check_input_tty("prompt", b"quux\xe9", "ascii", stdin_errors='strict',
2345+
expected="UnicodeDecodeError: 'ascii' codec can't decode "
2346+
"byte 0xe9 in position 4: ordinal not in "
2347+
"range(128)")
2348+
23232349
def test_input_no_stdout_fileno(self):
23242350
# Issue #24402: If stdin is the original terminal but stdout.fileno()
23252351
# fails, do not use the original stdout file descriptor
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:func:`input` now raises a ValueError when output on the terminal if the
2+
prompt contains embedded null characters instead of silently truncating it.

Python/bltinmodule.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,6 +2262,11 @@ builtin_input_impl(PyObject *module, PyObject *prompt)
22622262
goto _readline_errors;
22632263
assert(PyBytes_Check(po));
22642264
promptstr = PyBytes_AS_STRING(po);
2265+
if ((Py_ssize_t)strlen(promptstr) != PyBytes_GET_SIZE(po)) {
2266+
PyErr_SetString(PyExc_ValueError,
2267+
"input: prompt string cannot contain null characters");
2268+
goto _readline_errors;
2269+
}
22652270
}
22662271
else {
22672272
po = NULL;

0 commit comments

Comments
 (0)