|
50 | 50 | pyopenssl |
51 | 51 | """ |
52 | 52 |
|
| 53 | +import errno |
| 54 | +import os |
53 | 55 | import socket |
54 | 56 | import sys |
55 | 57 | import threading |
|
77 | 79 | from . import Adapter |
78 | 80 |
|
79 | 81 |
|
| 82 | +@contextlib.contextmanager |
| 83 | +def _morph_syscall_to_connection_error(method_name, /): |
| 84 | + """ |
| 85 | + Handle :exc:`OpenSSL.SSL.SysCallError` in a wrapped method. |
| 86 | +
|
| 87 | + This context manager catches and re-raises SSL system call errors |
| 88 | + with appropriate exception types. |
| 89 | +
|
| 90 | + Yields: |
| 91 | + None: Execution continues within the context block. |
| 92 | + """ # noqa: DAR301 |
| 93 | + try: |
| 94 | + yield |
| 95 | + except SSL.SysCallError as ssl_syscall_err: |
| 96 | + connection_error_map = { |
| 97 | + errno.EBADF: ConnectionError, # socket is gone? |
| 98 | + errno.ECONNABORTED: ConnectionAbortedError, |
| 99 | + errno.ECONNREFUSED: ConnectionRefusedError, |
| 100 | + errno.ECONNRESET: ConnectionResetError, |
| 101 | + errno.ENOTCONN: ConnectionError, |
| 102 | + errno.EPIPE: BrokenPipeError, |
| 103 | + errno.ESHUTDOWN: BrokenPipeError, |
| 104 | + } |
| 105 | + error_code = ssl_syscall_err.args[0] if ssl_syscall_err.args else None |
| 106 | + error_msg = ( |
| 107 | + os.strerror(error_code) |
| 108 | + if error_code is not None |
| 109 | + else repr(ssl_syscall_err) |
| 110 | + ) |
| 111 | + conn_err_cls = connection_error_map.get( |
| 112 | + error_code, |
| 113 | + ConnectionError, |
| 114 | + ) |
| 115 | + raise conn_err_cls( |
| 116 | + error_code, |
| 117 | + f'Faied to {method_name!s} the PyOpenSSL connection: {error_msg!s}', |
| 118 | + ) from ssl_syscall_err |
| 119 | + |
| 120 | + |
80 | 121 | class SSLFileobjectMixin: |
81 | 122 | """Base mixin for a TLS socket stream.""" |
82 | 123 |
|
@@ -269,6 +310,18 @@ def __init__(self, *args): |
269 | 310 | self._ssl_conn = SSL.Connection(*args) |
270 | 311 | self._lock = threading.RLock() |
271 | 312 |
|
| 313 | + @_morph_syscall_to_connection_error('close') |
| 314 | + def close(self): |
| 315 | + """Close the connection, translating OpenSSL errors for shutdown.""" |
| 316 | + with self._lock: |
| 317 | + return self._ssl_conn.close() |
| 318 | + |
| 319 | + @_morph_syscall_to_connection_error('shutdown') |
| 320 | + def shutdown(self): |
| 321 | + """Shutdown the connection, translating OpenSSL errors.""" |
| 322 | + with self._lock: |
| 323 | + return self._ssl_conn.shutdown() |
| 324 | + |
272 | 325 |
|
273 | 326 | class pyOpenSSLAdapter(Adapter): |
274 | 327 | """A wrapper for integrating :doc:`pyOpenSSL <pyopenssl:index>`.""" |
|
0 commit comments