Kaydet (Commit) 16dbbae2 authored tarafından R David Murray's avatar R David Murray

#18116: getpass no longer always falls back to stdin.

Also fixes a resource warning that occurred when the fallback is taken.

Patch by Serhiy Storchaka.

(We couldn't figure out how to write tests for this.)
üst acb362e2
...@@ -15,7 +15,11 @@ On the Mac EasyDialogs.AskPassword is used, if available. ...@@ -15,7 +15,11 @@ On the Mac EasyDialogs.AskPassword is used, if available.
# Guido van Rossum (Windows support and cleanup) # Guido van Rossum (Windows support and cleanup)
# Gregory P. Smith (tty support & GetPassWarning) # Gregory P. Smith (tty support & GetPassWarning)
import os, sys, warnings import contextlib
import io
import os
import sys
import warnings
__all__ = ["getpass","getuser","GetPassWarning"] __all__ = ["getpass","getuser","GetPassWarning"]
...@@ -38,28 +42,30 @@ def unix_getpass(prompt='Password: ', stream=None): ...@@ -38,28 +42,30 @@ def unix_getpass(prompt='Password: ', stream=None):
Always restores terminal settings before returning. Always restores terminal settings before returning.
""" """
fd = None
tty = None
passwd = None passwd = None
with contextlib.ExitStack() as stack:
try: try:
# Always try reading and writing directly on the tty first. # Always try reading and writing directly on the tty first.
fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY) fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY)
tty = os.fdopen(fd, 'w+', 1) tty = io.FileIO(fd, 'w+')
input = tty stack.enter_context(tty)
input = io.TextIOWrapper(tty)
stack.enter_context(input)
if not stream: if not stream:
stream = tty stream = input
except OSError as e: except OSError as e:
# If that fails, see if stdin can be controlled. # If that fails, see if stdin can be controlled.
stack.close()
try: try:
fd = sys.stdin.fileno() fd = sys.stdin.fileno()
except (AttributeError, ValueError): except (AttributeError, ValueError):
fd = None
passwd = fallback_getpass(prompt, stream) passwd = fallback_getpass(prompt, stream)
input = sys.stdin input = sys.stdin
if not stream: if not stream:
stream = sys.stderr stream = sys.stderr
if fd is not None: if fd is not None:
passwd = None
try: try:
old = termios.tcgetattr(fd) # a copy to save old = termios.tcgetattr(fd) # a copy to save
new = old[:] new = old[:]
...@@ -80,7 +86,9 @@ def unix_getpass(prompt='Password: ', stream=None): ...@@ -80,7 +86,9 @@ def unix_getpass(prompt='Password: ', stream=None):
raise raise
# We can't control the tty or stdin. Give up and use normal IO. # We can't control the tty or stdin. Give up and use normal IO.
# fallback_getpass() raises an appropriate warning. # fallback_getpass() raises an appropriate warning.
del input, tty # clean up unused file objects before blocking if stream is not input:
# clean up unused file objects before blocking
stack.close()
passwd = fallback_getpass(prompt, stream) passwd = fallback_getpass(prompt, stream)
stream.write('\n') stream.write('\n')
......
import getpass import getpass
import os import os
import unittest import unittest
from io import StringIO from io import BytesIO, StringIO
from unittest import mock from unittest import mock
from test import support from test import support
...@@ -88,7 +88,8 @@ class UnixGetpassTest(unittest.TestCase): ...@@ -88,7 +88,8 @@ class UnixGetpassTest(unittest.TestCase):
def test_uses_tty_directly(self): def test_uses_tty_directly(self):
with mock.patch('os.open') as open, \ with mock.patch('os.open') as open, \
mock.patch('os.fdopen'): mock.patch('io.FileIO') as fileio, \
mock.patch('io.TextIOWrapper') as textio:
# By setting open's return value to None the implementation will # By setting open's return value to None the implementation will
# skip code we don't care about in this test. We can mock this out # skip code we don't care about in this test. We can mock this out
# fully if an alternate implementation works differently. # fully if an alternate implementation works differently.
...@@ -96,10 +97,13 @@ class UnixGetpassTest(unittest.TestCase): ...@@ -96,10 +97,13 @@ class UnixGetpassTest(unittest.TestCase):
getpass.unix_getpass() getpass.unix_getpass()
open.assert_called_once_with('/dev/tty', open.assert_called_once_with('/dev/tty',
os.O_RDWR | os.O_NOCTTY) os.O_RDWR | os.O_NOCTTY)
fileio.assert_called_once_with(open.return_value, 'w+')
textio.assert_called_once_with(fileio.return_value)
def test_resets_termios(self): def test_resets_termios(self):
with mock.patch('os.open') as open, \ with mock.patch('os.open') as open, \
mock.patch('os.fdopen'), \ mock.patch('io.FileIO'), \
mock.patch('io.TextIOWrapper'), \
mock.patch('termios.tcgetattr') as tcgetattr, \ mock.patch('termios.tcgetattr') as tcgetattr, \
mock.patch('termios.tcsetattr') as tcsetattr: mock.patch('termios.tcsetattr') as tcsetattr:
open.return_value = 3 open.return_value = 3
...@@ -110,21 +114,23 @@ class UnixGetpassTest(unittest.TestCase): ...@@ -110,21 +114,23 @@ class UnixGetpassTest(unittest.TestCase):
def test_falls_back_to_fallback_if_termios_raises(self): def test_falls_back_to_fallback_if_termios_raises(self):
with mock.patch('os.open') as open, \ with mock.patch('os.open') as open, \
mock.patch('os.fdopen') as fdopen, \ mock.patch('io.FileIO') as fileio, \
mock.patch('io.TextIOWrapper') as textio, \
mock.patch('termios.tcgetattr'), \ mock.patch('termios.tcgetattr'), \
mock.patch('termios.tcsetattr') as tcsetattr, \ mock.patch('termios.tcsetattr') as tcsetattr, \
mock.patch('getpass.fallback_getpass') as fallback: mock.patch('getpass.fallback_getpass') as fallback:
open.return_value = 3 open.return_value = 3
fdopen.return_value = StringIO() fileio.return_value = BytesIO()
tcsetattr.side_effect = termios.error tcsetattr.side_effect = termios.error
getpass.unix_getpass() getpass.unix_getpass()
fallback.assert_called_once_with('Password: ', fallback.assert_called_once_with('Password: ',
fdopen.return_value) textio.return_value)
def test_flushes_stream_after_input(self): def test_flushes_stream_after_input(self):
# issue 7208 # issue 7208
with mock.patch('os.open') as open, \ with mock.patch('os.open') as open, \
mock.patch('os.fdopen'), \ mock.patch('io.FileIO'), \
mock.patch('io.TextIOWrapper'), \
mock.patch('termios.tcgetattr'), \ mock.patch('termios.tcgetattr'), \
mock.patch('termios.tcsetattr'): mock.patch('termios.tcsetattr'):
open.return_value = 3 open.return_value = 3
......
...@@ -142,6 +142,10 @@ Core and Builtins ...@@ -142,6 +142,10 @@ Core and Builtins
Library Library
------- -------
- Issue #18116: getpass was always getting an error when testing /dev/tty,
and thus was always falling back to stdin. It also leaked an open file
when it did so. Both of these issues are now fixed.
- Issue #17198: Fix a NameError in the dbm module. Patch by Valentina - Issue #17198: Fix a NameError in the dbm module. Patch by Valentina
Mukhamedzhanova. Mukhamedzhanova.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment