Skip to content

Commit

Permalink
Fix #719: don't raise SSLEOFError from SSLSocket.sendall(b'').
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jamadden committed Feb 19, 2016
1 parent 8ff9ee5 commit c0fa367
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 14 deletions.
8 changes: 7 additions & 1 deletion changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@
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)
=====================
Expand Down
4 changes: 4 additions & 0 deletions gevent/_socket2.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ def sendall(self, data, flags=0):
# so it should not call self._sock methods directly
data_memory = _get_memory(data)
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
# copy the underlying bytes the first time the builtin
Expand Down
10 changes: 8 additions & 2 deletions gevent/_socket3.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,17 +341,23 @@ def send(self, data, flags=0, timeout=timeout_default):
def sendall(self, data, flags=0):
# XXX When we run on PyPy3, see the notes in _socket2.py's sendall()
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:
data_sent = 0
while data_sent < len(data_memory):
while data_sent < len_data_memory:
data_sent += self.send(data_memory[data_sent:], flags)
else:
timeleft = self.timeout
end = time.time() + timeleft
data_sent = 0
while True:
data_sent += self.send(data_memory[data_sent:], flags, timeout=timeleft)
if data_sent >= len(data_memory):
if data_sent >= len_data_memory:
break
timeleft = end - time.time()
if timeleft <= 0:
Expand Down
38 changes: 30 additions & 8 deletions greentest/test__socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,15 @@ def cleanup(self):
pass
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.connect(('127.0.0.1', port or self.port))
sock.connect((host, port or self.port))
if timeout is not None:
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 = []

Expand All @@ -81,11 +82,18 @@ def accept_and_read():
os._exit(1)

server = Thread(target=accept_and_read)
client = self.create_connection()
client.sendall(data)
client.close()
client = self.create_connection(**client_args)

try:
getattr(client, client_method)(data)
finally:
client.shutdown(socket.SHUT_RDWR)
client.close()

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):
self._test_sendall(self.long_data)
Expand All @@ -98,6 +106,20 @@ def test_sendall_array(self):
data = array.array("B", self.long_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):

N = 100000
Expand Down
14 changes: 11 additions & 3 deletions greentest/test__ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ def setUp(self):
self.listener, _raw_listener = ssl_listener(('127.0.0.1', 0), self.privfile, self.certfile)
self.port = self.listener.getsockname()[1]

def create_connection(self):
return ssl.wrap_socket(super(TestSSL, self).create_connection())
def create_connection(self, *args, **kwargs):
return ssl.wrap_socket(super(TestSSL, self).create_connection(*args, **kwargs))

if not sys.platform.startswith('win32'):

Expand All @@ -38,7 +38,7 @@ def create_connection(self):
# to send a very large amount to make it timeout
_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

server_sock = []
Expand All @@ -56,6 +56,14 @@ def test_sendall_timeout0(self):
client.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):
raw_listener = socket.socket()
Expand Down

0 comments on commit c0fa367

Please sign in to comment.