diff -r 2472603af83e Lib/getpass.py --- a/Lib/getpass.py Mon Jun 03 22:09:14 2013 +0200 +++ b/Lib/getpass.py Tue Jun 04 23:03:34 2013 +0800 @@ -44,7 +44,7 @@ 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) + tty = os.fdopen(fd, 'wb+', buffering=0) input = tty if not stream: stream = tty @@ -73,6 +73,8 @@ finally: termios.tcsetattr(fd, tcsetattr_flags, old) stream.flush() # issue7208 + stream.write(b'\n') + tty.close() except termios.error: if passwd is not None: # _raw_input succeeded. The final tcsetattr failed. Reraise @@ -82,8 +84,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 +127,32 @@ stream = sys.stderr if not input: input = sys.stdin + try: + if input.isatty() and stream is not sys.stderr: + use_bytes = True + else: + use_bytes = False + except AttributeError: + use_bytes = False + if use_bytes: + try: + tty_encoding = os.device_encoding(input.fileno()) + except Exception: + import locale + tty_encoding = locale.getpreferredencoding() prompt = str(prompt) if prompt: - stream.write(prompt) + if use_bytes: + 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 use_bytes: + line = line.decode(tty_encoding) if line[-1] == '\n': line = line[:-1] return line diff -r 2472603af83e Lib/test/test_getpass.py --- a/Lib/test/test_getpass.py Mon Jun 03 22:09:14 2013 +0200 +++ b/Lib/test/test_getpass.py Tue Jun 04 23:03:34 2013 +0800 @@ -65,7 +65,7 @@ @mock.patch('sys.stdin') def test_uses_stdin_as_default_input(self, mock_input): - mock_input.readline.return_value = 'input_string' + mock_input.readline.return_value = b'input_string' getpass._raw_input(stream=StringIO()) mock_input.readline.assert_called_once_with() @@ -77,6 +77,24 @@ 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: + stream = mock.Mock(spec=StringIO) + input = StringIO('test\n') + encoding = 'utf-8' + prompt = 'enter password: ' + tty_file_descriptor = 3 + device_encoding.return_value = encoding + 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