[pypy-commit] pypy py3.3: Fix exception handling in _ssl: it now use distinct Exception classes.

amauryfa noreply at buildbot.pypy.org
Sun Jun 22 00:11:23 CEST 2014


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: py3.3
Changeset: r72128:18dd3240202b
Date: 2014-06-22 00:07 +0200
http://bitbucket.org/pypy/pypy/changeset/18dd3240202b/

Log:	Fix exception handling in _ssl: it now use distinct Exception
	classes. + Small hacks until "import ssl" succeeds.

diff --git a/pypy/module/_ssl/__init__.py b/pypy/module/_ssl/__init__.py
--- a/pypy/module/_ssl/__init__.py
+++ b/pypy/module/_ssl/__init__.py
@@ -5,7 +5,12 @@
     See the socket module for documentation."""
 
     interpleveldefs = {
-        'SSLError': 'interp_ssl.get_error(space)',
+        'SSLError': 'interp_ssl.get_error(space).w_error',
+        'SSLZeroReturnError': 'interp_ssl.get_error(space).w_ZeroReturnError',
+        'SSLWantReadError': 'interp_ssl.get_error(space).w_WantReadError',
+        'SSLWantWriteError': 'interp_ssl.get_error(space).w_WantWriteError',
+        'SSLEOFError': 'interp_ssl.get_error(space).w_EOFError',
+        'SSLSyscallError': 'interp_ssl.get_error(space).w_SyscallError',
         '_SSLSocket': 'interp_ssl.SSLSocket',
         '_SSLContext': 'interp_ssl.SSLContext',
         '_test_decode_cert': 'interp_ssl._test_decode_cert',
@@ -24,6 +29,8 @@
 
         if HAVE_OPENSSL_RAND:
             Module.interpleveldefs['RAND_add'] = "interp_ssl.RAND_add"
+            Module.interpleveldefs['RAND_bytes'] = "space.w_None"  # so far
+            Module.interpleveldefs['RAND_pseudo_bytes'] = "space.w_None"  # so far
             Module.interpleveldefs['RAND_status'] = "interp_ssl.RAND_status"
             Module.interpleveldefs['RAND_egd'] = "interp_ssl.RAND_egd"
 
diff --git a/pypy/module/_ssl/interp_ssl.py b/pypy/module/_ssl/interp_ssl.py
--- a/pypy/module/_ssl/interp_ssl.py
+++ b/pypy/module/_ssl/interp_ssl.py
@@ -11,6 +11,7 @@
 from rpython.rlib.rposix import get_errno, set_errno
 
 from pypy.module._socket import interp_socket
+from pypy.module.exceptions import interp_exceptions
 import weakref
 
 
@@ -62,10 +63,15 @@
 
 # protocol options
 constants["OP_ALL"] = SSL_OP_ALL & ~SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+constants["OP_CIPHER_SERVER_PREFERENCE"] = SSL_OP_CIPHER_SERVER_PREFERENCE
+constants["OP_SINGLE_DH_USE"] = SSL_OP_SINGLE_DH_USE
 constants["OP_NO_SSLv2"] = SSL_OP_NO_SSLv2
 constants["OP_NO_SSLv3"] = SSL_OP_NO_SSLv3
 constants["OP_NO_TLSv1"] = SSL_OP_NO_TLSv1
 constants["HAS_SNI"] = HAS_SNI
+constants["HAS_ECDH"] = True  # To break the test suite
+constants["HAS_NPN"] = True  # To break the test suite
+constants["HAS_TLS_UNIQUE"] = True  # To break the test suite
 
 # OpenSSL version
 def _parse_version(ver):
@@ -83,11 +89,12 @@
 constants["OPENSSL_VERSION"] = SSLEAY_VERSION
 constants["_OPENSSL_API_VERSION"] = _parse_version(libver)
 
-def ssl_error(space, msg, errno=0):
-    w_exception_class = get_error(space)
-    w_exception = space.call_function(w_exception_class,
+def ssl_error(space, msg, errno=0, w_errtype=None):
+    if w_errtype is None:
+        w_errtype = get_error(space).w_error
+    w_exception = space.call_function(w_errtype,
                                       space.wrap(errno), space.wrap(msg))
-    return OperationError(w_exception_class, w_exception)
+    return OperationError(w_errtype, w_exception)
 
 
 class SSLContext(W_Root):
@@ -958,16 +965,20 @@
         err = libssl_SSL_get_error(ss.ssl, ret)
     else:
         err = SSL_ERROR_SSL
+    w_errtype = None
     errstr = ""
     errval = 0
 
     if err == SSL_ERROR_ZERO_RETURN:
+        w_errtype = get_error(space).w_ZeroReturnError
         errstr = "TLS/SSL connection has been closed"
         errval = PY_SSL_ERROR_ZERO_RETURN
     elif err == SSL_ERROR_WANT_READ:
+        w_errtype = get_error(space).w_WantReadError
         errstr = "The operation did not complete (read)"
         errval = PY_SSL_ERROR_WANT_READ
     elif err == SSL_ERROR_WANT_WRITE:
+        w_errtype = get_error(space).w_WantWriteError
         errstr = "The operation did not complete (write)"
         errval = PY_SSL_ERROR_WANT_WRITE
     elif err == SSL_ERROR_WANT_X509_LOOKUP:
@@ -980,6 +991,7 @@
         e = libssl_ERR_get_error()
         if e == 0:
             if ret == 0 or ss.w_socket() is None:
+                w_errtype = get_error(space).w_EOFError
                 errstr = "EOF occurred in violation of protocol"
                 errval = PY_SSL_ERROR_EOF
             elif ret == -1:
@@ -987,6 +999,7 @@
                 error = rsocket.last_error()
                 return interp_socket.converted_error(space, error)
             else:
+                w_errtype = get_error(space).w_SyscallError
                 errstr = "Some I/O error occurred"
                 errval = PY_SSL_ERROR_SYSCALL
         else:
@@ -1003,17 +1016,44 @@
         errstr = "Invalid error code"
         errval = PY_SSL_ERROR_INVALID_ERROR_CODE
 
-    return ssl_error(space, errstr, errval)
+    return ssl_error(space, errstr, errval, w_errtype=w_errtype)
 
 
-class Cache:
+class W_Error(interp_exceptions.W_OSError):
+    "An error occurred in the SSL implementation."
+
+    def descr_str(self, space):
+        if space.isinstance_w(self.w_strerror, space.w_unicode):
+            return self.w_strerror
+        else:
+            return space.str(space.newtuple(self.args_w))
+
+W_Error.typedef = TypeDef(
+    "ssl.SSLError",
+    interp_exceptions.W_OSError.typedef,
+    __new__  = interp_exceptions._new(W_Error),
+    __doc__  = W_Error.__doc__,
+    __str__  = interp2app(W_Error.descr_str),
+    )
+
+
+class ErrorCache:
     def __init__(self, space):
-        w_socketerror = interp_socket.get_error(space, "error")
-        self.w_error = space.new_exception_class(
-            "_ssl.SSLError", w_socketerror)
+        self.w_error = space.gettypefor(W_Error)
+        self.w_ZeroReturnError = space.new_exception_class(
+            "ssl.SSLZeroReturnError", self.w_error)
+        self.w_WantReadError = space.new_exception_class(
+            "ssl.SSLWantReadError", self.w_error)
+        self.w_WantWriteError = space.new_exception_class(
+            "ssl.SSLWantWriteError", self.w_error)
+        self.w_EOFError = space.new_exception_class(
+            "ssl.SSLEOFError", self.w_error)
+        self.w_SyscallError = space.new_exception_class(
+            "ssl.SSLSyscallError", self.w_error)
 
 def get_error(space):
-    return space.fromcache(Cache).w_error
+    return space.fromcache(ErrorCache)
+
 
 @unwrap_spec(filename=str, verbose=bool)
 def _test_decode_cert(space, filename, verbose=True):
diff --git a/pypy/module/_ssl/test/test_ssl.py b/pypy/module/_ssl/test/test_ssl.py
--- a/pypy/module/_ssl/test/test_ssl.py
+++ b/pypy/module/_ssl/test/test_ssl.py
@@ -241,6 +241,37 @@
         ctx = _ssl._SSLContext(_ssl.PROTOCOL_TLSv1)
         assert _ssl.OP_ALL | _ssl.OP_NO_SSLv2 == ctx.options
 
+class AppTestSSLError:
+    spaceconfig = dict(usemodules=('_ssl', '_socket', 'binascii', 'thread'))
+
+    def test_str(self):
+        import _ssl
+        # The str() of a SSLError doesn't include the errno
+        e = _ssl.SSLError(1, "foo")
+        assert str(e) == "foo"
+        assert e.errno == 1
+        # Same for a subclass
+        e = _ssl.SSLZeroReturnError(1, "foo")
+        assert str(e) == "foo"
+        assert e.errno == 1
+
+    def test_subclass(self):
+        import ssl
+        import socket
+        # Check that the appropriate SSLError subclass is raised
+        # (this only tests one of them)
+        ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
+        with socket.socket() as s:
+            s.bind(("127.0.0.1", 0))
+            s.listen(5)
+            c = socket.socket()
+            c.connect(s.getsockname())
+            c.setblocking(False)
+            with ctx.wrap_socket(c, False, do_handshake_on_connect=False) as c:
+                exc = raises(ssl.SSLWantReadError, c.do_handshake)
+                assert str(exc.value).startswith("The operation did not complete (read)"), s
+                # For compatibility
+                assert exc.value.errno == ssl.SSL_ERROR_WANT_READ
 
 
 SSL_CERTIFICATE = """
diff --git a/rpython/rlib/ropenssl.py b/rpython/rlib/ropenssl.py
--- a/rpython/rlib/ropenssl.py
+++ b/rpython/rlib/ropenssl.py
@@ -82,6 +82,10 @@
     SSL_OP_NO_TLSv1 = rffi_platform.ConstantInteger("SSL_OP_NO_TLSv1")
     SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS = rffi_platform.ConstantInteger(
         "SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS")
+    SSL_OP_CIPHER_SERVER_PREFERENCE = rffi_platform.ConstantInteger(
+        "SSL_OP_CIPHER_SERVER_PREFERENCE")
+    SSL_OP_SINGLE_DH_USE = rffi_platform.ConstantInteger(
+        "SSL_OP_SINGLE_DH_USE")
     HAS_SNI = rffi_platform.Defined("SSL_CTRL_SET_TLSEXT_HOSTNAME")
     SSL_VERIFY_NONE = rffi_platform.ConstantInteger("SSL_VERIFY_NONE")
     SSL_VERIFY_PEER = rffi_platform.ConstantInteger("SSL_VERIFY_PEER")


More information about the pypy-commit mailing list