mirror of
https://github.com/python/cpython.git
synced 2025-11-24 04:17:38 +00:00
[3.14] gh-138514: getpass: restrict echo_char to a single ASCII character (GH-138591) (#138988)
Co-authored-by: Benjamin Johnson <benjohnson2040@gmail.com> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> Co-authored-by: Brian Schubert <brianm.schubert@gmail.com>
This commit is contained in:
parent
ce48f4c845
commit
37f8a63e39
5 changed files with 57 additions and 9 deletions
|
|
@ -27,9 +27,9 @@ The :mod:`getpass` module provides two functions:
|
||||||
|
|
||||||
The *echo_char* argument controls how user input is displayed while typing.
|
The *echo_char* argument controls how user input is displayed while typing.
|
||||||
If *echo_char* is ``None`` (default), input remains hidden. Otherwise,
|
If *echo_char* is ``None`` (default), input remains hidden. Otherwise,
|
||||||
*echo_char* must be a printable ASCII string and each typed character
|
*echo_char* must be a single printable ASCII character and each
|
||||||
is replaced by it. For example, ``echo_char='*'`` will display
|
typed character is replaced by it. For example, ``echo_char='*'`` will
|
||||||
asterisks instead of the actual input.
|
display asterisks instead of the actual input.
|
||||||
|
|
||||||
If echo free input is unavailable getpass() falls back to printing
|
If echo free input is unavailable getpass() falls back to printing
|
||||||
a warning message to *stream* and reading from ``sys.stdin`` and
|
a warning message to *stream* and reading from ``sys.stdin`` and
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@ def unix_getpass(prompt='Password: ', stream=None, *, echo_char=None):
|
||||||
prompt: Written on stream to ask for the input. Default: 'Password: '
|
prompt: Written on stream to ask for the input. Default: 'Password: '
|
||||||
stream: A writable file object to display the prompt. Defaults to
|
stream: A writable file object to display the prompt. Defaults to
|
||||||
the tty. If no tty is available defaults to sys.stderr.
|
the tty. If no tty is available defaults to sys.stderr.
|
||||||
echo_char: A string used to mask input (e.g., '*'). If None, input is
|
echo_char: A single ASCII character to mask input (e.g., '*').
|
||||||
hidden.
|
If None, input is hidden.
|
||||||
Returns:
|
Returns:
|
||||||
The seKr3t input.
|
The seKr3t input.
|
||||||
Raises:
|
Raises:
|
||||||
|
|
@ -144,10 +144,19 @@ def fallback_getpass(prompt='Password: ', stream=None, *, echo_char=None):
|
||||||
|
|
||||||
|
|
||||||
def _check_echo_char(echo_char):
|
def _check_echo_char(echo_char):
|
||||||
# ASCII excluding control characters
|
# Single-character ASCII excluding control characters
|
||||||
if echo_char and not (echo_char.isprintable() and echo_char.isascii()):
|
if echo_char is None:
|
||||||
raise ValueError("'echo_char' must be a printable ASCII string, "
|
return
|
||||||
f"got: {echo_char!r}")
|
if not isinstance(echo_char, str):
|
||||||
|
raise TypeError("'echo_char' must be a str or None, not "
|
||||||
|
f"{type(echo_char).__name__}")
|
||||||
|
if not (
|
||||||
|
len(echo_char) == 1
|
||||||
|
and echo_char.isprintable()
|
||||||
|
and echo_char.isascii()
|
||||||
|
):
|
||||||
|
raise ValueError("'echo_char' must be a single printable ASCII "
|
||||||
|
f"character, got: {echo_char!r}")
|
||||||
|
|
||||||
|
|
||||||
def _raw_input(prompt="", stream=None, input=None, echo_char=None):
|
def _raw_input(prompt="", stream=None, input=None, echo_char=None):
|
||||||
|
|
|
||||||
|
|
@ -201,5 +201,41 @@ class UnixGetpassTest(unittest.TestCase):
|
||||||
self.assertEqual('Password: *******\x08 \x08', mock_output.getvalue())
|
self.assertEqual('Password: *******\x08 \x08', mock_output.getvalue())
|
||||||
|
|
||||||
|
|
||||||
|
class GetpassEchoCharTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_accept_none(self):
|
||||||
|
getpass._check_echo_char(None)
|
||||||
|
|
||||||
|
@support.subTests('echo_char', ["*", "A", " "])
|
||||||
|
def test_accept_single_printable_ascii(self, echo_char):
|
||||||
|
getpass._check_echo_char(echo_char)
|
||||||
|
|
||||||
|
def test_reject_empty_string(self):
|
||||||
|
self.assertRaises(ValueError, getpass.getpass, echo_char="")
|
||||||
|
|
||||||
|
@support.subTests('echo_char', ["***", "AA", "aA*!"])
|
||||||
|
def test_reject_multi_character_strings(self, echo_char):
|
||||||
|
self.assertRaises(ValueError, getpass.getpass, echo_char=echo_char)
|
||||||
|
|
||||||
|
@support.subTests('echo_char', [
|
||||||
|
'\N{LATIN CAPITAL LETTER AE}', # non-ASCII single character
|
||||||
|
'\N{HEAVY BLACK HEART}', # non-ASCII multibyte character
|
||||||
|
])
|
||||||
|
def test_reject_non_ascii(self, echo_char):
|
||||||
|
self.assertRaises(ValueError, getpass.getpass, echo_char=echo_char)
|
||||||
|
|
||||||
|
@support.subTests('echo_char', [
|
||||||
|
ch for ch in map(chr, range(0, 128))
|
||||||
|
if not ch.isprintable()
|
||||||
|
])
|
||||||
|
def test_reject_non_printable_characters(self, echo_char):
|
||||||
|
self.assertRaises(ValueError, getpass.getpass, echo_char=echo_char)
|
||||||
|
|
||||||
|
# TypeError Rejection
|
||||||
|
@support.subTests('echo_char', [b"*", 0, 0.0, [], {}])
|
||||||
|
def test_reject_non_string(self, echo_char):
|
||||||
|
self.assertRaises(TypeError, getpass.getpass, echo_char=echo_char)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -903,6 +903,7 @@ Jim Jewett
|
||||||
Pedro Diaz Jimenez
|
Pedro Diaz Jimenez
|
||||||
Orjan Johansen
|
Orjan Johansen
|
||||||
Fredrik Johansson
|
Fredrik Johansson
|
||||||
|
Benjamin K. Johnson
|
||||||
Gregory K. Johnson
|
Gregory K. Johnson
|
||||||
Kent Johnson
|
Kent Johnson
|
||||||
Michael Johnson
|
Michael Johnson
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
Raise :exc:`ValueError` when a multi-character string is passed to the
|
||||||
|
*echo_char* parameter of :func:`getpass.getpass`. Patch by Benjamin Johnson.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue