mirror of
https://github.com/python/cpython.git
synced 2025-11-24 12:20:42 +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.
|
||||
If *echo_char* is ``None`` (default), input remains hidden. Otherwise,
|
||||
*echo_char* must be a printable ASCII string and each typed character
|
||||
is replaced by it. For example, ``echo_char='*'`` will display
|
||||
asterisks instead of the actual input.
|
||||
*echo_char* must be a single printable ASCII character and each
|
||||
typed character is replaced by it. For example, ``echo_char='*'`` will
|
||||
display asterisks instead of the actual input.
|
||||
|
||||
If echo free input is unavailable getpass() falls back to printing
|
||||
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: '
|
||||
stream: A writable file object to display the prompt. Defaults to
|
||||
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
|
||||
hidden.
|
||||
echo_char: A single ASCII character to mask input (e.g., '*').
|
||||
If None, input is hidden.
|
||||
Returns:
|
||||
The seKr3t input.
|
||||
Raises:
|
||||
|
|
@ -144,10 +144,19 @@ def fallback_getpass(prompt='Password: ', stream=None, *, echo_char=None):
|
|||
|
||||
|
||||
def _check_echo_char(echo_char):
|
||||
# ASCII excluding control characters
|
||||
if echo_char and not (echo_char.isprintable() and echo_char.isascii()):
|
||||
raise ValueError("'echo_char' must be a printable ASCII string, "
|
||||
f"got: {echo_char!r}")
|
||||
# Single-character ASCII excluding control characters
|
||||
if echo_char is None:
|
||||
return
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -201,5 +201,41 @@ class UnixGetpassTest(unittest.TestCase):
|
|||
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__":
|
||||
unittest.main()
|
||||
|
|
|
|||
|
|
@ -903,6 +903,7 @@ Jim Jewett
|
|||
Pedro Diaz Jimenez
|
||||
Orjan Johansen
|
||||
Fredrik Johansson
|
||||
Benjamin K. Johnson
|
||||
Gregory K. Johnson
|
||||
Kent 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