-
-
Notifications
You must be signed in to change notification settings - Fork 33.2k
gh-77065: Add optional keyword-only argument echo_char for getpass.getpass
#130496
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 17 commits
d2b3f74
8d9d83d
413a9ff
9ac2ae2
0ae2b81
da00d07
6c71075
946d718
e2351ab
75e37bc
cd08e68
bcdf95a
501d704
b6b822f
affd84a
977389a
59da53c
3c027d4
81e71a9
ba236ee
c608b7a
17a5891
08e8686
4319f23
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| """Utilities to get a password and/or the current user name. | ||
|
|
||
| getpass(prompt[, stream]) - Prompt for a password, with echo turned off. | ||
| getpass(prompt[, stream[, echochar]]) - Prompt for a password, with echo | ||
| turned off and optional keyboard feedback. | ||
| getuser() - Get the user name from the environment or password database. | ||
|
|
||
| GetPassWarning - This UserWarning is issued when getpass() cannot prevent | ||
|
|
@@ -25,13 +26,15 @@ | |
| class GetPassWarning(UserWarning): pass | ||
|
|
||
|
|
||
| def unix_getpass(prompt='Password: ', stream=None): | ||
| def unix_getpass(prompt='Password: ', stream=None, *, echochar=None): | ||
| """Prompt for a password, with echo turned off. | ||
|
|
||
| Args: | ||
| 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. | ||
| echochar: A string used to mask input (e.g., '*'). If None, input is | ||
| hidden. | ||
| Returns: | ||
| The seKr3t input. | ||
| Raises: | ||
|
|
@@ -40,6 +43,8 @@ def unix_getpass(prompt='Password: ', stream=None): | |
|
|
||
| Always restores terminal settings before returning. | ||
| """ | ||
| _check_echochar(echochar) | ||
|
|
||
| passwd = None | ||
| with contextlib.ExitStack() as stack: | ||
| try: | ||
|
|
@@ -68,12 +73,18 @@ def unix_getpass(prompt='Password: ', stream=None): | |
| old = termios.tcgetattr(fd) # a copy to save | ||
| new = old[:] | ||
| new[3] &= ~termios.ECHO # 3 == 'lflags' | ||
| if echochar: | ||
| new[3] &= ~termios.ICANON | ||
donBarbos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| tcsetattr_flags = termios.TCSAFLUSH | ||
| if hasattr(termios, 'TCSASOFT'): | ||
| tcsetattr_flags |= termios.TCSASOFT | ||
| try: | ||
| termios.tcsetattr(fd, tcsetattr_flags, new) | ||
| passwd = _raw_input(prompt, stream, input=input) | ||
| if echochar: | ||
| passwd = _input_with_echochar(prompt, stream, input, | ||
| echochar) | ||
| else: | ||
| passwd = _raw_input(prompt, stream, input=input) | ||
| finally: | ||
| termios.tcsetattr(fd, tcsetattr_flags, old) | ||
| stream.flush() # issue7208 | ||
|
|
@@ -93,10 +104,11 @@ def unix_getpass(prompt='Password: ', stream=None): | |
| return passwd | ||
|
|
||
|
|
||
| def win_getpass(prompt='Password: ', stream=None): | ||
| def win_getpass(prompt='Password: ', stream=None, *, echochar=None): | ||
| """Prompt for password with echo off, using Windows getwch().""" | ||
| if sys.stdin is not sys.__stdin__: | ||
| return fallback_getpass(prompt, stream) | ||
donBarbos marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| _check_echochar(echochar) | ||
|
|
||
| for c in prompt: | ||
| msvcrt.putwch(c) | ||
|
|
@@ -108,9 +120,15 @@ def win_getpass(prompt='Password: ', stream=None): | |
| if c == '\003': | ||
| raise KeyboardInterrupt | ||
| if c == '\b': | ||
| if echochar and pw: | ||
| msvcrt.putch('\b') | ||
| msvcrt.putch(' ') | ||
| msvcrt.putch('\b') | ||
| pw = pw[:-1] | ||
| else: | ||
| pw = pw + c | ||
| if echochar: | ||
| msvcrt.putwch(echochar) | ||
| msvcrt.putwch('\r') | ||
| msvcrt.putwch('\n') | ||
| return pw | ||
|
|
@@ -126,6 +144,12 @@ def fallback_getpass(prompt='Password: ', stream=None): | |
| return _raw_input(prompt, stream) | ||
|
|
||
|
|
||
| def _check_echochar(echochar): | ||
| # ASCII excluding control characters | ||
| if echochar and not (echochar.isprintable() and echochar.isascii()): | ||
| raise ValueError(f"'echochar' must be ASCII, got: {echochar!r}") | ||
donBarbos marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
|
|
||
| def _raw_input(prompt="", stream=None, input=None): | ||
| # This doesn't save the string in the GNU readline history. | ||
| if not stream: | ||
|
|
@@ -151,6 +175,33 @@ def _raw_input(prompt="", stream=None, input=None): | |
| return line | ||
|
|
||
|
|
||
| def _input_with_echochar(prompt, stream, input, echochar): | ||
|
||
| if not stream: | ||
| stream = sys.stderr | ||
| if not input: | ||
| input = sys.stdin | ||
| prompt = str(prompt) | ||
| stream.write(prompt) | ||
| stream.flush() | ||
| passwd = "" | ||
| while True: | ||
| char = input.read(1) | ||
| if char == '\n' or char == '\r': | ||
| break | ||
| if char == '\x03': | ||
donBarbos marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| raise KeyboardInterrupt | ||
| if char == '\x7f' or char == '\b': | ||
picnixz marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if passwd: | ||
| stream.write("\b \b") | ||
| stream.flush() | ||
| passwd = passwd[:-1] | ||
| else: | ||
| passwd += char | ||
| stream.write(echochar) | ||
| stream.flush() | ||
| return passwd | ||
|
|
||
|
|
||
| def getuser(): | ||
| """Get the username from the environment or password database. | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| Add keyword-only optional argument *echochar* for :meth:`getpass.getpass` | ||
| for optional visual keyboard feedback support. Patch by Semyon Moroz. |
Uh oh!
There was an error while loading. Please reload this page.