|
50 | 50 | pyopenssl |
51 | 51 | """ |
52 | 52 |
|
| 53 | +import io |
53 | 54 | import socket |
54 | 55 | import sys |
55 | 56 | import threading |
|
74 | 75 | errors, |
75 | 76 | server as cheroot_server, |
76 | 77 | ) |
77 | | -from ..makefile import StreamReader, StreamWriter |
78 | 78 | from . import Adapter |
79 | 79 |
|
80 | 80 |
|
@@ -168,14 +168,6 @@ def send(self, *args, **kwargs): |
168 | 168 | ) |
169 | 169 |
|
170 | 170 |
|
171 | | -class SSLFileobjectStreamReader(SSLFileobjectMixin, StreamReader): |
172 | | - """SSL file object attached to a socket object.""" |
173 | | - |
174 | | - |
175 | | -class SSLFileobjectStreamWriter(SSLFileobjectMixin, StreamWriter): |
176 | | - """SSL file object attached to a socket object.""" |
177 | | - |
178 | | - |
179 | 171 | class SSLConnectionProxyMeta: |
180 | 172 | """Metaclass for generating a bunch of proxy methods.""" |
181 | 173 |
|
@@ -345,9 +337,11 @@ def wrap(self, sock): |
345 | 337 | ) |
346 | 338 |
|
347 | 339 | conn = SSLConnection(self.context, sock) |
348 | | - |
349 | 340 | conn.set_accept_state() # Tell OpenSSL this is a server connection |
350 | | - return conn, self._environ.copy() |
| 341 | + |
| 342 | + # Wrap the SSLConnection to provide standard socket interface |
| 343 | + tls_socket = TLSSocket(conn) |
| 344 | + return tls_socket, self._environ.copy() |
351 | 345 |
|
352 | 346 | def _password_callback( |
353 | 347 | self, |
@@ -450,17 +444,101 @@ def get_environ(self): |
450 | 444 | return ssl_environ |
451 | 445 |
|
452 | 446 | def makefile(self, sock, mode='r', bufsize=-1): |
453 | | - """Return socket file object.""" |
454 | | - cls = ( |
455 | | - SSLFileobjectStreamReader |
456 | | - if 'r' in mode |
457 | | - else SSLFileobjectStreamWriter |
| 447 | + """ |
| 448 | + Return socket file object. |
| 449 | +
|
| 450 | + ``makefile`` is now deprecated and will be removed in a future |
| 451 | + version. |
| 452 | + """ |
| 453 | + _warn( |
| 454 | + 'The `makefile` method is deprecated and will be removed in a future version. ' |
| 455 | + 'The connection socket should be fully wrapped by the adapter ' |
| 456 | + 'before being passed to the HTTPConnection constructor.', |
| 457 | + DeprecationWarning, |
| 458 | + stacklevel=2, |
458 | 459 | ) |
459 | | - # sock is an pyopenSSL.SSLConnection instance here |
460 | | - if SSL and isinstance(sock, SSLConnection): |
461 | | - wrapped_socket = cls(sock, mode, bufsize) |
462 | | - wrapped_socket.ssl_timeout = sock.gettimeout() |
463 | | - return wrapped_socket |
464 | | - # This is from past: |
465 | | - # TODO: figure out what it's meant for |
466 | | - return cheroot_server.CP_fileobject(sock, mode, bufsize) |
| 460 | + |
| 461 | + return sock.makefile(mode, bufsize) |
| 462 | + |
| 463 | + |
| 464 | +class RawIOWrapperMixin: |
| 465 | + """ |
| 466 | + Provide methods to satisfy the io.RawIOBase interface. |
| 467 | +
|
| 468 | + Delegates I/O to the core socket methods recv_into and send, |
| 469 | + which are implemented by TLSSocket. |
| 470 | + """ |
| 471 | + |
| 472 | + def readable(self): |
| 473 | + """Return whether object supports reading.""" |
| 474 | + return True |
| 475 | + |
| 476 | + def writable(self): |
| 477 | + """Return whether object supports writing.""" |
| 478 | + return True |
| 479 | + |
| 480 | + def readinto(self, b): |
| 481 | + """Read bytes into a pre-allocated buffer (I/O interface).""" |
| 482 | + # This relies on the concrete class implementing recv_into |
| 483 | + return self.recv_into(b) |
| 484 | + |
| 485 | + def write(self, b): |
| 486 | + """Write bytes (I/O interface).""" |
| 487 | + # This relies on the concrete class implementing send |
| 488 | + return self.send(b) |
| 489 | + |
| 490 | + |
| 491 | +class TLSSocket(RawIOWrapperMixin, SSLFileobjectMixin, io.RawIOBase): |
| 492 | + """ |
| 493 | + Wrap pyOpenSSL SSL.Connection to work with StreamReader/StreamWriter. |
| 494 | +
|
| 495 | + Handles OpenSSL-specific errors internally so that standard Python I/O |
| 496 | + classes can use it transparently. |
| 497 | + """ |
| 498 | + |
| 499 | + def __init__(self, ssl_conn, ssl_timeout=None, ssl_retry=0.01): |
| 500 | + """Initialize with an SSL.Connection object.""" |
| 501 | + self._ssl_conn = ssl_conn |
| 502 | + self._sock = ssl_conn._socket # Store reference to raw TCP socket |
| 503 | + self._lock = threading.RLock() |
| 504 | + self.ssl_timeout = ssl_timeout or 3.0 |
| 505 | + self.ssl_retry = ssl_retry |
| 506 | + |
| 507 | + # Socket I/O |
| 508 | + # _safe_call is delegated to the SSLFileobjectMixin |
| 509 | + |
| 510 | + def recv_into(self, buffer, nbytes=None): |
| 511 | + """Receive data into a buffer (socket interface).""" |
| 512 | + with self._lock: |
| 513 | + return self._safe_call( |
| 514 | + True, # is_reader=True |
| 515 | + self._ssl_conn.recv_into, |
| 516 | + buffer, |
| 517 | + nbytes, |
| 518 | + ) |
| 519 | + |
| 520 | + def send(self, data): |
| 521 | + """Send data (socket interface).""" |
| 522 | + with self._lock: |
| 523 | + return self._safe_call( |
| 524 | + False, # is_reader=False |
| 525 | + self._ssl_conn.send, |
| 526 | + data, |
| 527 | + ) |
| 528 | + |
| 529 | + def fileno(self): |
| 530 | + """Return the file descriptor.""" |
| 531 | + return self._ssl_conn.fileno() |
| 532 | + |
| 533 | + def _decref_socketios(self): |
| 534 | + """Shutdown the connection.""" |
| 535 | + return self._ssl_conn._decref_socketios() |
| 536 | + |
| 537 | + def shutdown(self, how): |
| 538 | + """Shutdown the connection.""" |
| 539 | + return self._ssl_conn.shutdown(how) |
| 540 | + |
| 541 | + def close(self): |
| 542 | + """Close the connection.""" |
| 543 | + super().close() |
| 544 | + self._ssl_conn.close() |
0 commit comments