mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	Issue #23197, asyncio: On SSL handshake failure, check if the waiter is
cancelled before setting its exception. * Add unit tests for this case. * Cleanup also sslproto.py
This commit is contained in:
		
							parent
							
								
									f651a60407
								
							
						
					
					
						commit
						177e9f0855
					
				
					 4 changed files with 65 additions and 7 deletions
				
			
		|  | @ -750,7 +750,7 @@ def _on_handshake(self, start_time): | ||||||
|             self._loop.remove_reader(self._sock_fd) |             self._loop.remove_reader(self._sock_fd) | ||||||
|             self._loop.remove_writer(self._sock_fd) |             self._loop.remove_writer(self._sock_fd) | ||||||
|             self._sock.close() |             self._sock.close() | ||||||
|             if self._waiter is not None: |             if self._waiter is not None and not self._waiter.cancelled(): | ||||||
|                 self._waiter.set_exception(exc) |                 self._waiter.set_exception(exc) | ||||||
|             if isinstance(exc, Exception): |             if isinstance(exc, Exception): | ||||||
|                 return |                 return | ||||||
|  |  | ||||||
|  | @ -530,10 +530,11 @@ def _on_handshake_complete(self, handshake_exc): | ||||||
|         self._in_handshake = False |         self._in_handshake = False | ||||||
| 
 | 
 | ||||||
|         sslobj = self._sslpipe.ssl_object |         sslobj = self._sslpipe.ssl_object | ||||||
|         peercert = None if handshake_exc else sslobj.getpeercert() |  | ||||||
|         try: |         try: | ||||||
|             if handshake_exc is not None: |             if handshake_exc is not None: | ||||||
|                 raise handshake_exc |                 raise handshake_exc | ||||||
|  | 
 | ||||||
|  |             peercert = sslobj.getpeercert() | ||||||
|             if not hasattr(self._sslcontext, 'check_hostname'): |             if not hasattr(self._sslcontext, 'check_hostname'): | ||||||
|                 # Verify hostname if requested, Python 3.4+ uses check_hostname |                 # Verify hostname if requested, Python 3.4+ uses check_hostname | ||||||
|                 # and checks the hostname in do_handshake() |                 # and checks the hostname in do_handshake() | ||||||
|  | @ -551,7 +552,7 @@ def _on_handshake_complete(self, handshake_exc): | ||||||
|                                    self, exc_info=True) |                                    self, exc_info=True) | ||||||
|             self._transport.close() |             self._transport.close() | ||||||
|             if isinstance(exc, Exception): |             if isinstance(exc, Exception): | ||||||
|                 if self._waiter is not None: |                 if self._waiter is not None and not self._waiter.cancelled(): | ||||||
|                     self._waiter.set_exception(exc) |                     self._waiter.set_exception(exc) | ||||||
|                 return |                 return | ||||||
|             else: |             else: | ||||||
|  |  | ||||||
|  | @ -1148,16 +1148,28 @@ def test_on_handshake_exc(self): | ||||||
|         self.assertTrue(self.sslsock.close.called) |         self.assertTrue(self.sslsock.close.called) | ||||||
| 
 | 
 | ||||||
|     def test_on_handshake_base_exc(self): |     def test_on_handshake_base_exc(self): | ||||||
|  |         waiter = asyncio.Future(loop=self.loop) | ||||||
|         transport = _SelectorSslTransport( |         transport = _SelectorSslTransport( | ||||||
|             self.loop, self.sock, self.protocol, self.sslcontext) |             self.loop, self.sock, self.protocol, self.sslcontext, waiter) | ||||||
|         transport._waiter = asyncio.Future(loop=self.loop) |  | ||||||
|         exc = BaseException() |         exc = BaseException() | ||||||
|         self.sslsock.do_handshake.side_effect = exc |         self.sslsock.do_handshake.side_effect = exc | ||||||
|         with test_utils.disable_logger(): |         with test_utils.disable_logger(): | ||||||
|             self.assertRaises(BaseException, transport._on_handshake, 0) |             self.assertRaises(BaseException, transport._on_handshake, 0) | ||||||
|         self.assertTrue(self.sslsock.close.called) |         self.assertTrue(self.sslsock.close.called) | ||||||
|         self.assertTrue(transport._waiter.done()) |         self.assertTrue(waiter.done()) | ||||||
|         self.assertIs(exc, transport._waiter.exception()) |         self.assertIs(exc, waiter.exception()) | ||||||
|  | 
 | ||||||
|  |     def test_cancel_handshake(self): | ||||||
|  |         # Python issue #23197: cancelling an handshake must not raise an | ||||||
|  |         # exception or log an error, even if the handshake failed | ||||||
|  |         waiter = asyncio.Future(loop=self.loop) | ||||||
|  |         transport = _SelectorSslTransport( | ||||||
|  |             self.loop, self.sock, self.protocol, self.sslcontext, waiter) | ||||||
|  |         waiter.cancel() | ||||||
|  |         exc = ValueError() | ||||||
|  |         self.sslsock.do_handshake.side_effect = exc | ||||||
|  |         with test_utils.disable_logger(): | ||||||
|  |             transport._on_handshake(0) | ||||||
| 
 | 
 | ||||||
|     def test_pause_resume_reading(self): |     def test_pause_resume_reading(self): | ||||||
|         tr = self._make_one() |         tr = self._make_one() | ||||||
|  |  | ||||||
							
								
								
									
										45
									
								
								Lib/test/test_asyncio/test_sslproto.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								Lib/test/test_asyncio/test_sslproto.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,45 @@ | ||||||
|  | """Tests for asyncio/sslproto.py.""" | ||||||
|  | 
 | ||||||
|  | import unittest | ||||||
|  | from unittest import mock | ||||||
|  | 
 | ||||||
|  | import asyncio | ||||||
|  | from asyncio import sslproto | ||||||
|  | from asyncio import test_utils | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class SslProtoHandshakeTests(test_utils.TestCase): | ||||||
|  | 
 | ||||||
|  |     def setUp(self): | ||||||
|  |         self.loop = asyncio.new_event_loop() | ||||||
|  |         self.set_event_loop(self.loop) | ||||||
|  | 
 | ||||||
|  |     def test_cancel_handshake(self): | ||||||
|  |         # Python issue #23197: cancelling an handshake must not raise an | ||||||
|  |         # exception or log an error, even if the handshake failed | ||||||
|  |         sslcontext = test_utils.dummy_ssl_context() | ||||||
|  |         app_proto = asyncio.Protocol() | ||||||
|  |         waiter = asyncio.Future(loop=self.loop) | ||||||
|  |         ssl_proto = sslproto.SSLProtocol(self.loop, app_proto, sslcontext, | ||||||
|  |                                          waiter) | ||||||
|  |         handshake_fut = asyncio.Future(loop=self.loop) | ||||||
|  | 
 | ||||||
|  |         def do_handshake(callback): | ||||||
|  |             exc = Exception() | ||||||
|  |             callback(exc) | ||||||
|  |             handshake_fut.set_result(None) | ||||||
|  |             return [] | ||||||
|  | 
 | ||||||
|  |         waiter.cancel() | ||||||
|  |         transport = mock.Mock() | ||||||
|  |         sslpipe = mock.Mock() | ||||||
|  |         sslpipe.do_handshake.side_effect = do_handshake | ||||||
|  |         with mock.patch('asyncio.sslproto._SSLPipe', return_value=sslpipe): | ||||||
|  |             ssl_proto.connection_made(transport) | ||||||
|  | 
 | ||||||
|  |         with test_utils.disable_logger(): | ||||||
|  |             self.loop.run_until_complete(handshake_fut) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | if __name__ == '__main__': | ||||||
|  |     unittest.main() | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Victor Stinner
						Victor Stinner