Skip to content

Commit 9618049

Browse files
committed
Map PyOpenSSL SysCallErrors to standard Python ConnectionErrors
1 parent 4a8dc43 commit 9618049

File tree

2 files changed

+56
-0
lines changed

2 files changed

+56
-0
lines changed

cheroot/ssl/pyopenssl.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
pyopenssl
5151
"""
5252

53+
import errno
54+
import os
5355
import socket
5456
import sys
5557
import threading
@@ -77,6 +79,45 @@
7779
from . import Adapter
7880

7981

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+
80121
class SSLFileobjectMixin:
81122
"""Base mixin for a TLS socket stream."""
82123

@@ -269,6 +310,18 @@ def __init__(self, *args):
269310
self._ssl_conn = SSL.Connection(*args)
270311
self._lock = threading.RLock()
271312

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+
272325

273326
class pyOpenSSLAdapter(Adapter):
274327
"""A wrapper for integrating :doc:`pyOpenSSL <pyopenssl:index>`."""
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
OpenSSL system call errors are now intercepted and reraised as standard Python ConnectionErrors.
2+
3+
-- by :user:`julianz-`

0 commit comments

Comments
 (0)