Commit c0fa367b authored by Jason Madden's avatar Jason Madden

Fix #719: don't raise SSLEOFError from SSLSocket.sendall(b'').

Add unit tests for sending empty data using sendall, sendall with a
timeout, and send. Note that SSLSocket.send(b'') *does* raise EOFError,
just like the standard library.
parent 8ff9ee5e
...@@ -7,7 +7,13 @@ ...@@ -7,7 +7,13 @@
1.1rc5 (unreleased) 1.1rc5 (unreleased)
=================== ===================
- TBD. - SSL: Attempting to send empty data using the ``sendall`` method of a
gevent SSL socket that hase a timeout now returns immediately (like
the standard library does), instead of incorrectly raising
``SSLEOFError``. (Note that sending empty data with the ``send`` method
*does* raise ``SSLEOFError`` in both gevent and the standard
library.) Reported in :issue:`719` by Mustafa Atik, with a
reproducible test case provided by Timo Savola.
1.1rc4 (Feb 16, 2016) 1.1rc4 (Feb 16, 2016)
===================== =====================
......
...@@ -360,6 +360,10 @@ class socket(object): ...@@ -360,6 +360,10 @@ class socket(object):
# so it should not call self._sock methods directly # so it should not call self._sock methods directly
data_memory = _get_memory(data) data_memory = _get_memory(data)
len_data_memory = len(data_memory) len_data_memory = len(data_memory)
if not len_data_memory:
# Don't send empty data, can cause SSL EOFError.
# See issue 719
return 0
# On PyPy up through 2.6.0, subviews of a memoryview() object # On PyPy up through 2.6.0, subviews of a memoryview() object
# copy the underlying bytes the first time the builtin # copy the underlying bytes the first time the builtin
......
...@@ -341,9 +341,15 @@ class socket(object): ...@@ -341,9 +341,15 @@ class socket(object):
def sendall(self, data, flags=0): def sendall(self, data, flags=0):
# XXX When we run on PyPy3, see the notes in _socket2.py's sendall() # XXX When we run on PyPy3, see the notes in _socket2.py's sendall()
data_memory = _get_memory(data) data_memory = _get_memory(data)
len_data_memory = len(data_memory)
if not len_data_memory:
# Don't try to send empty data at all, no point, and breaks ssl
# See issue 719
return 0
if self.timeout is None: if self.timeout is None:
data_sent = 0 data_sent = 0
while data_sent < len(data_memory): while data_sent < len_data_memory:
data_sent += self.send(data_memory[data_sent:], flags) data_sent += self.send(data_memory[data_sent:], flags)
else: else:
timeleft = self.timeout timeleft = self.timeout
...@@ -351,7 +357,7 @@ class socket(object): ...@@ -351,7 +357,7 @@ class socket(object):
data_sent = 0 data_sent = 0
while True: while True:
data_sent += self.send(data_memory[data_sent:], flags, timeout=timeleft) data_sent += self.send(data_memory[data_sent:], flags, timeout=timeleft)
if data_sent >= len(data_memory): if data_sent >= len_data_memory:
break break
timeleft = end - time.time() timeleft = end - time.time()
if timeleft <= 0: if timeleft <= 0:
......
...@@ -58,14 +58,15 @@ class TestTCP(greentest.TestCase): ...@@ -58,14 +58,15 @@ class TestTCP(greentest.TestCase):
pass pass
del self.listener del self.listener
def create_connection(self, port=None, timeout=None): def create_connection(self, host='127.0.0.1', port=None, timeout=None):
sock = socket.socket() sock = socket.socket()
sock.connect(('127.0.0.1', port or self.port)) sock.connect((host, port or self.port))
if timeout is not None: if timeout is not None:
sock.settimeout(timeout) sock.settimeout(timeout)
return sock return self._close_on_teardown(sock)
def _test_sendall(self, data): def _test_sendall(self, data, match_data=None, client_method='sendall',
**client_args):
read_data = [] read_data = []
...@@ -81,11 +82,18 @@ class TestTCP(greentest.TestCase): ...@@ -81,11 +82,18 @@ class TestTCP(greentest.TestCase):
os._exit(1) os._exit(1)
server = Thread(target=accept_and_read) server = Thread(target=accept_and_read)
client = self.create_connection() client = self.create_connection(**client_args)
client.sendall(data)
try:
getattr(client, client_method)(data)
finally:
client.shutdown(socket.SHUT_RDWR)
client.close() client.close()
server.join() server.join()
self.assertEqual(read_data[0], self.long_data) if match_data is None:
match_data = self.long_data
self.assertEqual(read_data[0], match_data)
def test_sendall_str(self): def test_sendall_str(self):
self._test_sendall(self.long_data) self._test_sendall(self.long_data)
...@@ -98,6 +106,20 @@ class TestTCP(greentest.TestCase): ...@@ -98,6 +106,20 @@ class TestTCP(greentest.TestCase):
data = array.array("B", self.long_data) data = array.array("B", self.long_data)
self._test_sendall(data) self._test_sendall(data)
def test_sendall_empty(self):
data = b''
self._test_sendall(data, data)
def test_sendall_empty_with_timeout(self):
# Issue 719
data = b''
self._test_sendall(data, data, timeout=10)
def test_empty_send(self):
# Issue 719
data = b''
self._test_sendall(data, data, client_method='send')
def test_fullduplex(self): def test_fullduplex(self):
N = 100000 N = 100000
......
...@@ -25,8 +25,8 @@ class TestSSL(test__socket.TestTCP): ...@@ -25,8 +25,8 @@ class TestSSL(test__socket.TestTCP):
self.listener, _raw_listener = ssl_listener(('127.0.0.1', 0), self.privfile, self.certfile) self.listener, _raw_listener = ssl_listener(('127.0.0.1', 0), self.privfile, self.certfile)
self.port = self.listener.getsockname()[1] self.port = self.listener.getsockname()[1]
def create_connection(self): def create_connection(self, *args, **kwargs):
return ssl.wrap_socket(super(TestSSL, self).create_connection()) return ssl.wrap_socket(super(TestSSL, self).create_connection(*args, **kwargs))
if not sys.platform.startswith('win32'): if not sys.platform.startswith('win32'):
...@@ -38,7 +38,7 @@ class TestSSL(test__socket.TestTCP): ...@@ -38,7 +38,7 @@ class TestSSL(test__socket.TestTCP):
# to send a very large amount to make it timeout # to send a very large amount to make it timeout
_test_sendall_data = data_sent = b'hello' * 100000000 _test_sendall_data = data_sent = b'hello' * 100000000
def test_sendall_timeout0(self): def test_ssl_sendall_timeout0(self):
# Issue #317: SSL_WRITE_PENDING in some corner cases # Issue #317: SSL_WRITE_PENDING in some corner cases
server_sock = [] server_sock = []
...@@ -56,6 +56,14 @@ class TestSSL(test__socket.TestTCP): ...@@ -56,6 +56,14 @@ class TestSSL(test__socket.TestTCP):
client.close() client.close()
server_sock[0][0].close() server_sock[0][0].close()
def test_empty_send(self):
# Sending empty bytes with the 'send' method
# raises ssl.SSLEOFError in the stdlib. (just ssl.SSLError on
# py26). Issue 719
expected = getattr(ssl, 'SSLEOFError', ssl.SSLError)
self.assertRaises(expected, self._test_sendall,
b'',
client_method='send')
def ssl_listener(address, private_key, certificate): def ssl_listener(address, private_key, certificate):
raw_listener = socket.socket() raw_listener = socket.socket()
......
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