diff -r 6b189f29b1e6 Lib/getpass.py --- a/Lib/getpass.py Wed Jun 05 18:37:50 2013 -0400 +++ b/Lib/getpass.py Thu Jun 06 21:37:24 2013 +0800 @@ -43,8 +43,8 @@ passwd = None try: # Always try reading and writing directly on the tty first. - fd = os.open('/dev/tty', os.O_RDWR|os.O_NOCTTY) - tty = os.fdopen(fd, 'w+', 1) + fd = os.open('/dev/tty', os.O_RDWR | os.O_NOCTTY) + tty = os.fdopen(fd, 'wb+', buffering=0) input = tty if not stream: stream = tty @@ -73,6 +73,11 @@ finally: termios.tcsetattr(fd, tcsetattr_flags, old) stream.flush() # issue7208 + if stream.isatty() and stream not in [sys.stdout, sys.stderr]: + stream.write(b'\n') + else: + stream.write('\n') + tty.close() except termios.error: if passwd is not None: # _raw_input succeeded. The final tcsetattr failed. Reraise @@ -82,8 +87,8 @@ # fallback_getpass() raises an appropriate warning. del input, tty # clean up unused file objects before blocking passwd = fallback_getpass(prompt, stream) + stream.write('\n') - stream.write('\n') return passwd @@ -125,14 +130,26 @@ stream = sys.stderr if not input: input = sys.stdin + if input.isatty() and input is not sys.stdin: + tty_encoding = os.device_encoding(input.fileno()) + if not tty_encoding: + import locale + tty_encoding = locale.getpreferredencoding() + if not tty_encoding: + tty_encoding = 'latin-1' prompt = str(prompt) if prompt: - stream.write(prompt) + if stream.isatty() and stream not in [sys.stdout, sys.stderr]: + stream.write(bytes(prompt, tty_encoding)) + else: + stream.write(prompt) stream.flush() # NOTE: The Python C API calls flockfile() (and unlock) during readline. line = input.readline() if not line: raise EOFError + if input.isatty() and input is not sys.stdin: + line = line.decode(tty_encoding) if line[-1] == '\n': line = line[:-1] return line diff -r 6b189f29b1e6 Lib/test/test_getpass.py --- a/Lib/test/test_getpass.py Wed Jun 05 18:37:50 2013 -0400 +++ b/Lib/test/test_getpass.py Thu Jun 06 21:37:24 2013 +0800 @@ -52,6 +52,7 @@ def test_flushes_stream_after_prompt(self): # see issue 1703 stream = mock.Mock(spec=StringIO) + stream.isatty = lambda: False input = StringIO('input_string') getpass._raw_input('some_prompt', stream, input=input) stream.flush.assert_called_once_with() @@ -77,6 +78,25 @@ input = StringIO('test\n') self.assertEqual('test', getpass._raw_input(input=input)) + def test_uses_bytes_with_tty_file(self): + with mock.patch('os.device_encoding') as device_encoding, \ + mock.patch('builtins.bytes') as bytes_mock: + encoding = 'utf-8' + prompt = 'enter password: ' + tty_file_descriptor = 3 + device_encoding.return_value = encoding + stream = mock.Mock(spec=StringIO) + stream.isatty = lambda: True + input = StringIO('test\n') + input.isatty = lambda: True + input.fileno = lambda: tty_file_descriptor + input.readline = lambda: b'test\n' + self.assertEqual('test', getpass._raw_input(prompt=prompt, + stream=stream, input=input)) + device_encoding.assert_called_once_with(tty_file_descriptor) + bytes_mock.assert_called_once_with(prompt, encoding) + stream.write.assert_called_once() + # Some of these tests are a bit white-box. The functional requirement is that # the password input be taken directly from the tty, and that it not be echoed