Implement Connection.recv_into.
diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py
index 7b1cbc1..b694578 100644
--- a/OpenSSL/SSL.py
+++ b/OpenSSL/SSL.py
@@ -1027,6 +1027,45 @@
read = recv
+ def recv_into(self, buffer, nbytes=None, flags=None):
+ """
+ Receive data on the connection and store the data into a buffer rather
+ than creating a new string.
+
+ :param buffer: The buffer to copy into.
+ :param nbytes: (optional) The maximum number of bytes to read into the
+ buffer. If not present, defaults to the size of the buffer. If
+ larger than the size of the buffer, is reduced to the size of the
+ buffer.
+ :param flags: (optional) Included for compatibility with the socket
+ API, the value is ignored.
+ :return: The number of bytes read into the buffer.
+ """
+ if nbytes is None:
+ nbytes = len(buffer)
+ else:
+ nbytes = min(nbytes, len(buffer))
+
+ # We need to create a temporary buffer. This is annoying, it would be
+ # better if we could pass memoryviews straight into the SSL_read call,
+ # but right now we can't. Revisit this if CFFI gets that ability.
+ buf = _ffi.new("char[]", nbytes)
+ result = _lib.SSL_read(self._ssl, buf, nbytes)
+ self._raise_ssl_error(self._ssl, result)
+
+ # This strange line is all to avoid a memory copy. The buffer protocol
+ # should allow us to assign a CFFI buffer to the LHS of this line, but
+ # on CPython 3.3+ that segfaults. As a workaround, we can temporarily
+ # wrap it in a memoryview, except on Python 2.6 which doesn't have a
+ # memoryview type.
+ try:
+ buffer[:result] = memoryview(_ffi.buffer(buf, result))
+ except NameError:
+ buffer[:result] = _ffi.buffer(buf, result)
+
+ return result
+
+
def _handle_bio_errors(self, bio, result):
if _lib.BIO_should_retry(bio):
if _lib.BIO_should_read(bio):
diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py
index 6409b8e..daf1924 100644
--- a/OpenSSL/test/test_ssl.py
+++ b/OpenSSL/test/test_ssl.py
@@ -2217,6 +2217,98 @@
+class ConnectionRecvIntoTests(TestCase, _LoopbackMixin):
+ """
+ Tests for :py:obj:`Connection.recv_into`
+ """
+ def test_recv_into_no_length(self):
+ """
+ Test that :py:obj:`Connection.recv_into` can be safely passed an object
+ using the buffer protocol.
+ """
+ server, client = self._loopback()
+ server.send(b('xy'))
+ buffer = bytearray(5)
+
+ self.assertEquals(client.recv_into(buffer), 2)
+ self.assertEquals(buffer, bytearray(b('xy\x00\x00\x00')))
+
+
+ def test_recv_into_respects_length(self):
+ """
+ Test that :py:obj:`Connection.recv_into` respects the nbytes
+ parameter and doesn't copy more than that number of bytes in.
+ """
+ server, client = self._loopback()
+ server.send(b('abcdefghij'))
+ buffer = bytearray(10)
+
+ self.assertEquals(client.recv_into(buffer, 5), 5)
+ self.assertEquals(buffer, bytearray(b('abcde\x00\x00\x00\x00\x00')))
+
+
+ def test_recv_into_doesnt_overfill(self):
+ """
+ Test that :py:obj:`Connection.recv_into` doesn't overfill an object
+ implementing the buffer protocol.
+ """
+ server, client = self._loopback()
+ server.send(b('abcdefghij'))
+ buffer = bytearray(5)
+
+ self.assertEquals(client.recv_into(buffer), 5)
+ self.assertEquals(buffer, bytearray(b('abcde')))
+
+
+ try:
+ memoryview
+ except NameError:
+ "cannot test recv_into memoryview without memoryview"
+ else:
+ def test_recv_into_memoryview_no_length(self):
+ """
+ Test that :py:obj:`Connection.recv_into` can be safely passed a
+ memoryview.
+ """
+ server, client = self._loopback()
+ server.send(b('xy'))
+ buffer = bytearray(5)
+ mv = memoryview(buffer)
+
+ self.assertEquals(client.recv_into(mv), 2)
+ self.assertEquals(buffer, bytearray(b('xy\x00\x00\x00')))
+
+
+ def test_recv_into_memoryview_respects_length(self):
+ """
+ Test that :py:obj:`Connection.recv_into` respects the nbytes
+ parameter and doesn't copy more than that number of bytes in.
+ """
+ server, client = self._loopback()
+ server.send(b('abcdefghij'))
+ buffer = bytearray(10)
+ mv = memoryview(buffer)
+
+ self.assertEquals(client.recv_into(mv, 5), 5)
+ self.assertEquals(buffer,
+ bytearray(b('abcde\x00\x00\x00\x00\x00')))
+
+
+ def test_recv_into_memoryview_doesnt_overfill(self):
+ """
+ Test that :py:obj:`Connection.recv_into` doesn't overfill a
+ memoryview.
+ """
+ server, client = self._loopback()
+ server.send(b('abcdefghij'))
+ buffer = bytearray(5)
+ mv = memoryview(buffer)
+
+ self.assertEquals(client.recv_into(mv), 5)
+ self.assertEquals(buffer, bytearray(b('abcde')))
+
+
+
class ConnectionSendallTests(TestCase, _LoopbackMixin):
"""
Tests for :py:obj:`Connection.sendall`.