diff -r ec3077e23b7d Lib/nntplib.py --- a/Lib/nntplib.py Sun Oct 05 21:20:51 2014 -0400 +++ b/Lib/nntplib.py Sat Feb 14 03:53:02 2015 +0000 @@ -1042,11 +1042,18 @@ self.host = host self.port = port self.sock = socket.create_connection((host, port), timeout) - file = self.sock.makefile("rwb") - _NNTPBase.__init__(self, file, host, - readermode, timeout) - if user or usenetrc: - self.login(user, password, usenetrc) + file = None + try: + file = self.sock.makefile("rwb") + _NNTPBase.__init__(self, file, host, + readermode, timeout) + if user or usenetrc: + self.login(user, password, usenetrc) + except: + if file: + file.close() + self.sock.close() + raise def _close(self): try: @@ -1066,12 +1073,19 @@ in default port and the `ssl_context` argument for SSL connections. """ self.sock = socket.create_connection((host, port), timeout) - self.sock = _encrypt_on(self.sock, ssl_context, host) - file = self.sock.makefile("rwb") - _NNTPBase.__init__(self, file, host, - readermode=readermode, timeout=timeout) - if user or usenetrc: - self.login(user, password, usenetrc) + file = None + try: + self.sock = _encrypt_on(self.sock, ssl_context, host) + file = self.sock.makefile("rwb") + _NNTPBase.__init__(self, file, host, + readermode=readermode, timeout=timeout) + if user or usenetrc: + self.login(user, password, usenetrc) + except: + if file: + file.close() + self.sock.close() + raise def _close(self): try: diff -r ec3077e23b7d Lib/test/test_nntplib.py --- a/Lib/test/test_nntplib.py Sun Oct 05 21:20:51 2014 -0400 +++ b/Lib/test/test_nntplib.py Sat Feb 14 03:53:02 2015 +0000 @@ -8,10 +8,15 @@ from test import support from nntplib import NNTP, GroupInfo import nntplib +import gc try: import ssl except ImportError: ssl = None +try: + import threading +except ImportError: + threading = None TIMEOUT = 30 @@ -1425,5 +1430,132 @@ target_api.append('NNTP_SSL') self.assertEqual(set(nntplib.__all__), set(target_api)) +class DummyServerNNTPMixin: + + def _check_constructor_error_conditions( + self, expected_nntp_error, expected_nntp_error_repr, + login=None, password=None): + nntp = None + class NNTP(nntplib.NNTP): + def __init__(self, *pos, **kw): + nonlocal nntp + nntp = self # Save a reference to the created object + super().__init__(*pos, **kw) + with support.check_warnings(record=True) as recorded: + with self.assertRaisesRegex(expected_nntp_error, + expected_nntp_error_repr): + NNTP(self.host, self.port, login, password, timeout=5) + support.gc_collect() + self.assertEqual([], recorded.warnings) + self.assertLess(nntp.sock.fileno(), 0, 'fd should be closed') + + def test_bad_welcome(self): + #Test a bad welcome message + welcome_str = 'Bad Welcome' + self.expect_a_send_b = [ + #crlf is intentionally left out + (b'', bytes(welcome_str, "ascii")) + ] + self._check_constructor_error_conditions( + nntplib.NNTPProtocolError, welcome_str) + + def test_service_temporarily_unavailable(self): + #Test service temporarily unavailable + welcome_str = '400 Service temporarily unavilable' + self.expect_a_send_b = [ + (b'', bytes(welcome_str + '\r\n', 'ascii')) + ] + self._check_constructor_error_conditions( + nntplib.NNTPTemporaryError, welcome_str) + + def test_service_permanently_unavailable(self): + #Test service permanently unavailable + welcome_str = '502 Service permanently unavilable' + self.expect_a_send_b = [ + (b'', bytes(welcome_str + '\r\n', 'ascii')) + ] + self._check_constructor_error_conditions( + nntplib.NNTPPermanentError, welcome_str) + + def test_bad_capabilities(self): + #Test a bad capabilities response + capabilities_response = '201 bad capability' + self.expect_a_send_b = [ + (b'', b'201 NNTP Service Ready, posting prohibited\r\n'), + (b'CAPABILITIES\r\n', bytes(capabilities_response + '\r\n', + 'ascii')) + ] + self._check_constructor_error_conditions( + nntplib.NNTPReplyError, capabilities_response) + + def test_login_aborted(self): + #Test a bad authinfo response + login = 't@e.com' + password = 'python' + authinfo_response = '503 Mechanism not recognized' + self.expect_a_send_b = [ + (b'', b'201 NNTP Service Ready, posting prohibited\r\n'), + (b'CAPABILITIES\r\n', bytes('101 Capability list:\r\n'\ + 'VERSION 2 3\r\nREADER\r\n' \ + 'LIST ACTIVE NEWSGROUPS\r\n' \ + 'AUTHINFO USER SASL\r\n' \ + 'SASL CRAM-MD5 DIGEST-MD5 GSSAPI'\ + ' PLAIN EXTERNAL\r\n.\r\n', + 'ascii')), + #nntplib sends lower case authinfo command + (b'authinfo user t@e.com\r\n', bytes(authinfo_response +'\r\n', + 'ascii')) + ] + self._check_constructor_error_conditions( + nntplib.NNTPPermanentError, authinfo_response, + login, password) + +@unittest.skipUnless(threading, 'Threading required for these tests.') +class DummyServerNNTPTests(DummyServerNNTPMixin, unittest.TestCase): + + def setUp(self): + self.evt = threading.Event() + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.settimeout(5) + self.host = support.HOST + self.port = support.bind_port(self.sock) + self.server_thread = threading.Thread(target=self.server) + self.server_thread.start() + # Wait for the server to be ready. + self.evt.wait() + self.evt.clear() + + def tearDown(self): + self.server_thread.join() + + def server(self): + # This method sets the evt 3 times: + # 1) when the connection is ready to be accepted. + # 2) when it is safe for the caller to close the connection + # 3) when we have closed the socket + self.sock.listen() + # (1) Signal the caller that we are ready to accept the connection. + self.evt.set() + try: + conn, addr = self.sock.accept() + self.addCleanup(conn.close) + except socket.timeout: + pass + else: + #expect_a_send_b should be set by caller + #before it initiates a connection. + for a, b in self.expect_a_send_b: + if a: + data = conn.recv(len(a)) + self.assertEqual(a, data) + if b: + conn.sendall(b) + conn.shutdown(socket.SHUT_WR) + # (2) Signal the caller that it is safe to close the socket. + self.evt.set() + conn.close() + finally: + self.sock.close() + if __name__ == "__main__": unittest.main()