Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Doc/library/getpass.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 15 additions & 6 deletions Lib/getpass.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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):
Expand Down
36 changes: 36 additions & 0 deletions Lib/test/test_getpass.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,5 +201,41 @@ def test_control_chars_with_echo_char(self):
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()
1 change: 1 addition & 0 deletions Misc/ACKS
Original file line number Diff line number Diff line change
Expand Up @@ -903,6 +903,7 @@ Jim Jewett
Pedro Diaz Jimenez
Orjan Johansen
Fredrik Johansson
Benjamin K. Johnson
Gregory K. Johnson
Kent Johnson
Michael Johnson
Expand Down
Original file line number Diff line number Diff line change
@@ -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