[pypy-commit] pypy stdlib-2.7.9: Lots of improvement to the SSLError class.

amauryfa noreply at buildbot.pypy.org
Sun Jan 25 19:57:05 CET 2015


Author: Amaury Forgeot d'Arc <amauryfa at gmail.com>
Branch: stdlib-2.7.9
Changeset: r75517:ad8cb0a374b6
Date: 2015-01-25 19:55 +0100
http://bitbucket.org/pypy/pypy/changeset/ad8cb0a374b6/

Log:	Lots of improvement to the SSLError class.

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
@@ -3,12 +3,15 @@
 from rpython.rlib.ropenssl import *
 from rpython.rlib.rposix import get_errno, set_errno
 from rpython.rlib.rweakref import RWeakValueDictionary
+from rpython.rlib.objectmodel import specialize
 from rpython.rtyper.lltypesystem import lltype, rffi
 
 from pypy.interpreter.baseobjspace import W_Root
 from pypy.interpreter.error import OperationError, oefmt, wrap_oserror
 from pypy.interpreter.gateway import interp2app, unwrap_spec
 from pypy.interpreter.typedef import TypeDef, GetSetProperty
+from pypy.module._ssl.ssl_data import (
+    LIBRARY_CODES_TO_NAMES, ERROR_CODES_TO_NAMES)
 from pypy.module._socket import interp_socket
 
 
@@ -91,13 +94,32 @@
 constants["OPENSSL_VERSION"] = SSLEAY_VERSION
 
 
-def ssl_error(space, msg, errno=0, exc='w_sslerror'):
-    w_exception_class = get_exception_class(space, exc)
-    if not errno:
-        w_exception = space.call_function(w_exception_class, space.wrap(msg))
-    else:
+def ssl_error(space, msg, errno=0, w_errtype=None, errcode=0):
+    reason_str = None
+    lib_str = None
+    if errcode:
+        err_lib = libssl_ERR_GET_LIB(errcode)
+        err_reason = libssl_ERR_GET_REASON(errcode)
+        reason_str = ERROR_CODES_TO_NAMES.get((err_lib, err_reason), None)
+        lib_str = LIBRARY_CODES_TO_NAMES.get(err_lib, None)
+        msg = rffi.charp2str(libssl_ERR_reason_error_string(errcode))
+    if not msg:
+        msg = "unknown error"
+    if reason_str and lib_str:
+        msg = "[%s: %s] %s" % (lib_str, reason_str, msg)
+    elif lib_str:
+        msg = "[%s] %s" % (lib_str, msg)
+
+    w_exception_class = w_errtype or get_exception_class(space, 'w_sslerror')
+    if errno or errcode:
         w_exception = space.call_function(w_exception_class,
                                           space.wrap(errno), space.wrap(msg))
+    else:
+        w_exception = space.call_function(w_exception_class, space.wrap(msg))
+    space.setattr(w_exception, space.wrap("reason"),
+                  space.wrap(reason_str) if reason_str else space.w_None)
+    space.setattr(w_exception, space.wrap("library"),
+                  space.wrap(lib_str) if lib_str else space.w_None)
     return OperationError(w_exception_class, w_exception)
 
 class SSLNpnProtocols(object):
@@ -794,22 +816,25 @@
 
     if ss is None:
         errval = libssl_ERR_peek_last_error()
-        errstr = rffi.charp2str(libssl_ERR_error_string(errval, None))
-        return ssl_error(space, errstr, errval)
+        return ssl_error(space, None, errcode=errval)
     elif ss.ssl:
         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_exception_class(space, 'w_sslzeroreturnerror')
         errstr = "TLS/SSL connection has been closed"
         errval = PY_SSL_ERROR_ZERO_RETURN
     elif err == SSL_ERROR_WANT_READ:
+        w_errtype = get_exception_class(space, 'w_sslwantreaderror')
         errstr = "The operation did not complete (read)"
         errval = PY_SSL_ERROR_WANT_READ
     elif err == SSL_ERROR_WANT_WRITE:
+        w_errtype = get_exception_class(space, 'w_sslwantwriteerror')
         errstr = "The operation did not complete (write)"
         errval = PY_SSL_ERROR_WANT_WRITE
     elif err == SSL_ERROR_WANT_X509_LOOKUP:
@@ -829,6 +854,7 @@
                 error = rsocket.last_error()
                 return interp_socket.converted_error(space, error)
             else:
+                w_errtype = get_exception_class(space, 'w_sslsyscallerror')
                 errstr = "Some I/O error occurred"
                 errval = PY_SSL_ERROR_SYSCALL
         else:
@@ -845,7 +871,13 @@
         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)
+
+def SSLError_descr_str(space, w_exc):
+    w_strerror = space.getattr(w_exc, space.wrap("strerror"))
+    if not space.is_none(w_strerror):
+        return w_strerror
+    return space.str(space.getattr(w_exc, space.wrap("args")))
 
 
 class Cache:
@@ -853,6 +885,8 @@
         w_socketerror = interp_socket.get_error(space, "error")
         self.w_sslerror = space.new_exception_class(
             "_ssl.SSLError", w_socketerror)
+        space.setattr(self.w_sslerror, space.wrap('__str__'),
+                      space.wrap(interp2app(SSLError_descr_str)))
         self.w_sslzeroreturnerror = space.new_exception_class(
             "_ssl.SSLZeroReturnError", self.w_sslerror)
         self.w_sslwantreaderror = space.new_exception_class(
@@ -865,6 +899,7 @@
             "_ssl.SSLEOFError", self.w_sslerror)
 
 
+ at specialize.memo()
 def get_exception_class(space, name):
     return getattr(space.fromcache(Cache), name)
 
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
@@ -301,6 +301,61 @@
         raises(TypeError, ctx.load_dh_params, None)
         raises(_ssl.SSLError, ctx.load_dh_params, self.keycert)
 
+
+class AppTestSSLError:
+    spaceconfig = dict(usemodules=('_ssl', '_socket', 'thread'))
+
+    def setup_class(cls):
+        tmpfile = udir / "tmpfile.pem"
+        tmpfile.write(SSL_CERTIFICATE + SSL_PRIVATE_KEY)
+        cls.w_keycert = cls.space.wrap(str(tmpfile))
+
+    def test_str(self):
+        # The str() of a SSLError doesn't include the errno
+        import _ssl
+        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_lib_reason(self):
+        # Test the library and reason attributes
+        import _ssl
+        ctx = _ssl._SSLContext(_ssl.PROTOCOL_TLSv1)
+        exc = raises(_ssl.SSLError, ctx.load_dh_params, self.keycert)
+        assert exc.value.library == 'PEM'
+        assert exc.value.reason == 'NO_START_LINE'
+        s = str(exc.value)
+        assert s.startswith("[PEM: NO_START_LINE] no start line")
+
+    def test_subclass(self):
+        # Check that the appropriate SSLError subclass is raised
+        # (this only tests one of them)
+        import _ssl, _socket
+        ctx = _ssl._SSLContext(_ssl.PROTOCOL_TLSv1)
+        s = _socket.socket()
+        try:
+            s.bind(("127.0.0.1", 0))
+            s.listen(5)
+            c = _socket.socket()
+            c.connect(s.getsockname())
+            c.setblocking(False)
+            
+            c = ctx._wrap_socket(c, False)
+            try:
+                exc = raises(_ssl.SSLWantReadError, c.do_handshake)
+                msg= str(exc.value)
+                assert msg.startswith("The operation did not complete (read)")
+                # For compatibility
+                assert exc.value.errno == _ssl.SSL_ERROR_WANT_READ
+            finally:
+                c.shutdown()
+        finally:
+            s.close()
+
 SSL_CERTIFICATE = """
 -----BEGIN CERTIFICATE-----
 MIICVDCCAb2gAwIBAgIJANfHOBkZr8JOMA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV
diff --git a/rpython/rlib/ropenssl.py b/rpython/rlib/ropenssl.py
--- a/rpython/rlib/ropenssl.py
+++ b/rpython/rlib/ropenssl.py
@@ -337,6 +337,7 @@
 ssl_external('ERR_get_error', [], rffi.INT)
 ssl_external('ERR_peek_last_error', [], rffi.INT)
 ssl_external('ERR_error_string', [rffi.ULONG, rffi.CCHARP], rffi.CCHARP)
+ssl_external('ERR_reason_error_string', [rffi.ULONG], rffi.CCHARP)
 ssl_external('ERR_clear_error', [], lltype.Void)
 ssl_external('ERR_GET_LIB', [rffi.ULONG], rffi.INT, macro=True)
 ssl_external('ERR_GET_REASON', [rffi.ULONG], rffi.INT, macro=True)


More information about the pypy-commit mailing list