11"""Utilities to get a password and/or the current user name.
22
3- getpass(prompt[, stream]) - Prompt for a password, with echo turned off.
3+ getpass(prompt[, stream[, echo_char]]) - Prompt for a password, with echo
4+ turned off and optional keyboard feedback.
45getuser() - Get the user name from the environment or password database.
56
67GetPassWarning - This UserWarning is issued when getpass() cannot prevent
2526class GetPassWarning (UserWarning ): pass
2627
2728
28- def unix_getpass (prompt = 'Password: ' , stream = None ):
29+ def unix_getpass (prompt = 'Password: ' , stream = None , * , echo_char = None ):
2930 """Prompt for a password, with echo turned off.
3031
3132 Args:
3233 prompt: Written on stream to ask for the input. Default: 'Password: '
3334 stream: A writable file object to display the prompt. Defaults to
3435 the tty. If no tty is available defaults to sys.stderr.
36+ echo_char: A string used to mask input (e.g., '*'). If None, input is
37+ hidden.
3538 Returns:
3639 The seKr3t input.
3740 Raises:
@@ -40,6 +43,8 @@ def unix_getpass(prompt='Password: ', stream=None):
4043
4144 Always restores terminal settings before returning.
4245 """
46+ _check_echo_char (echo_char )
47+
4348 passwd = None
4449 with contextlib .ExitStack () as stack :
4550 try :
@@ -68,12 +73,16 @@ def unix_getpass(prompt='Password: ', stream=None):
6873 old = termios .tcgetattr (fd ) # a copy to save
6974 new = old [:]
7075 new [3 ] &= ~ termios .ECHO # 3 == 'lflags'
76+ if echo_char :
77+ new [3 ] &= ~ termios .ICANON
7178 tcsetattr_flags = termios .TCSAFLUSH
7279 if hasattr (termios , 'TCSASOFT' ):
7380 tcsetattr_flags |= termios .TCSASOFT
7481 try :
7582 termios .tcsetattr (fd , tcsetattr_flags , new )
76- passwd = _raw_input (prompt , stream , input = input )
83+ passwd = _raw_input (prompt , stream , input = input ,
84+ echo_char = echo_char )
85+
7786 finally :
7887 termios .tcsetattr (fd , tcsetattr_flags , old )
7988 stream .flush () # issue7208
@@ -93,10 +102,11 @@ def unix_getpass(prompt='Password: ', stream=None):
93102 return passwd
94103
95104
96- def win_getpass (prompt = 'Password: ' , stream = None ):
105+ def win_getpass (prompt = 'Password: ' , stream = None , * , echo_char = None ):
97106 """Prompt for password with echo off, using Windows getwch()."""
98107 if sys .stdin is not sys .__stdin__ :
99108 return fallback_getpass (prompt , stream )
109+ _check_echo_char (echo_char )
100110
101111 for c in prompt :
102112 msvcrt .putwch (c )
@@ -108,9 +118,15 @@ def win_getpass(prompt='Password: ', stream=None):
108118 if c == '\003 ' :
109119 raise KeyboardInterrupt
110120 if c == '\b ' :
121+ if echo_char and pw :
122+ msvcrt .putch ('\b ' )
123+ msvcrt .putch (' ' )
124+ msvcrt .putch ('\b ' )
111125 pw = pw [:- 1 ]
112126 else :
113127 pw = pw + c
128+ if echo_char :
129+ msvcrt .putwch (echo_char )
114130 msvcrt .putwch ('\r ' )
115131 msvcrt .putwch ('\n ' )
116132 return pw
@@ -126,7 +142,14 @@ def fallback_getpass(prompt='Password: ', stream=None):
126142 return _raw_input (prompt , stream )
127143
128144
129- def _raw_input (prompt = "" , stream = None , input = None ):
145+ def _check_echo_char (echo_char ):
146+ # ASCII excluding control characters
147+ if echo_char and not (echo_char .isprintable () and echo_char .isascii ()):
148+ raise ValueError ("'echo_char' must be a printable ASCII string, "
149+ f"got: { echo_char !r} " )
150+
151+
152+ def _raw_input (prompt = "" , stream = None , input = None , echo_char = None ):
130153 # This doesn't save the string in the GNU readline history.
131154 if not stream :
132155 stream = sys .stderr
@@ -143,6 +166,8 @@ def _raw_input(prompt="", stream=None, input=None):
143166 stream .write (prompt )
144167 stream .flush ()
145168 # NOTE: The Python C API calls flockfile() (and unlock) during readline.
169+ if echo_char :
170+ return _readline_with_echo_char (stream , input , echo_char )
146171 line = input .readline ()
147172 if not line :
148173 raise EOFError
@@ -151,6 +176,35 @@ def _raw_input(prompt="", stream=None, input=None):
151176 return line
152177
153178
179+ def _readline_with_echo_char (stream , input , echo_char ):
180+ passwd = ""
181+ eof_pressed = False
182+ while True :
183+ char = input .read (1 )
184+ if char == '\n ' or char == '\r ' :
185+ break
186+ elif char == '\x03 ' :
187+ raise KeyboardInterrupt
188+ elif char == '\x7f ' or char == '\b ' :
189+ if passwd :
190+ stream .write ("\b \b " )
191+ stream .flush ()
192+ passwd = passwd [:- 1 ]
193+ elif char == '\x04 ' :
194+ if eof_pressed :
195+ break
196+ else :
197+ eof_pressed = True
198+ elif char == '\x00 ' :
199+ continue
200+ else :
201+ passwd += char
202+ stream .write (echo_char )
203+ stream .flush ()
204+ eof_pressed = False
205+ return passwd
206+
207+
154208def getuser ():
155209 """Get the username from the environment or password database.
156210
0 commit comments