]> granicus.if.org Git - python/commitdiff
Issue #11183: Add finer-grained exceptions to the ssl module, so that
authorAntoine Pitrou <solipsis@pitrou.net>
Thu, 27 Oct 2011 21:56:55 +0000 (23:56 +0200)
committerAntoine Pitrou <solipsis@pitrou.net>
Thu, 27 Oct 2011 21:56:55 +0000 (23:56 +0200)
you don't have to inspect the exception's attributes in the common case.

Doc/library/ssl.rst
Lib/ssl.py
Lib/test/test_ssl.py
Misc/NEWS
Modules/_ssl.c

index 9f3760e87aadeb935db3752584da11ce411c6366..5232f1bdeac9cb10cf0095a559e4a974188a3f68 100644 (file)
@@ -59,6 +59,48 @@ Functions, Constants, and Exceptions
    .. versionchanged:: 3.3
       :exc:`SSLError` used to be a subtype of :exc:`socket.error`.
 
+.. exception:: SSLZeroReturnError
+
+   A subclass of :exc:`SSLError` raised when trying to read or write and
+   the SSL connection has been closed cleanly.  Note that this doesn't
+   mean that the underlying transport (read TCP) has been closed.
+
+   .. versionadded:: 3.3
+
+.. exception:: SSLWantReadError
+
+   A subclass of :exc:`SSLError` raised by a :ref:`non-blocking SSL socket
+   <ssl-nonblocking>` when trying to read or write data, but more data needs
+   to be received on the underlying TCP transport before the request can be
+   fulfilled.
+
+   .. versionadded:: 3.3
+
+.. exception:: SSLWantWriteError
+
+   A subclass of :exc:`SSLError` raised by a :ref:`non-blocking SSL socket
+   <ssl-nonblocking>` when trying to read or write data, but more data needs
+   to be sent on the underlying TCP transport before the request can be
+   fulfilled.
+
+   .. versionadded:: 3.3
+
+.. exception:: SSLSyscallError
+
+   A subclass of :exc:`SSLError` raised when a system error was encountered
+   while trying to fulfill an operation on a SSL socket.  Unfortunately,
+   there is no easy way to inspect the original errno number.
+
+   .. versionadded:: 3.3
+
+.. exception:: SSLEOFError
+
+   A subclass of :exc:`SSLError` raised when the SSL connection has been
+   terminated abrupted.  Generally, you shouldn't try to reuse the underlying
+   transport when this error is encountered.
+
+   .. versionadded:: 3.3
+
 .. exception:: CertificateError
 
    Raised to signal an error with a certificate (such as mismatching
index 39cef2c699b3f61aa07aa56026277eaa915cf64c..76f68f0020b9a9254661208a07b3d3f92481be53 100644 (file)
@@ -60,7 +60,11 @@ import re
 import _ssl             # if we can't import it, let the error propagate
 
 from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION
-from _ssl import _SSLContext, SSLError
+from _ssl import _SSLContext
+from _ssl import (
+    SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,
+    SSLSyscallError, SSLEOFError,
+    )
 from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
 from _ssl import OP_ALL, OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_TLSv1
 from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes
index 25f3e4fe6b9d13bf10141b355c00e9f69caae702..327bb8465d7513a17e9d15acb864050232e8d6ef 100644 (file)
@@ -619,13 +619,10 @@ class NetworkedTests(unittest.TestCase):
                     try:
                         s.do_handshake()
                         break
-                    except ssl.SSLError as err:
-                        if err.args[0] == ssl.SSL_ERROR_WANT_READ:
-                            select.select([s], [], [], 5.0)
-                        elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
-                            select.select([], [s], [], 5.0)
-                        else:
-                            raise
+                    except ssl.SSLWantReadError:
+                        select.select([s], [], [], 5.0)
+                    except ssl.SSLWantWriteError:
+                        select.select([], [s], [], 5.0)
                 # SSL established
                 self.assertTrue(s.getpeercert())
             finally:
@@ -745,13 +742,10 @@ class NetworkedTests(unittest.TestCase):
                     count += 1
                     s.do_handshake()
                     break
-                except ssl.SSLError as err:
-                    if err.args[0] == ssl.SSL_ERROR_WANT_READ:
-                        select.select([s], [], [])
-                    elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
-                        select.select([], [s], [])
-                    else:
-                        raise
+                except ssl.SSLWantReadError:
+                    select.select([s], [], [])
+                except ssl.SSLWantWriteError:
+                    select.select([], [s], [])
             s.close()
             if support.verbose:
                 sys.stdout.write("\nNeeded %d calls to do_handshake() to establish session.\n" % count)
@@ -1030,12 +1024,11 @@ else:
                 def _do_ssl_handshake(self):
                     try:
                         self.socket.do_handshake()
-                    except ssl.SSLError as err:
-                        if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
-                                           ssl.SSL_ERROR_WANT_WRITE):
-                            return
-                        elif err.args[0] == ssl.SSL_ERROR_EOF:
-                            return self.handle_close()
+                    except (ssl.SSLWantReadError, ssl.SSLWantWriteError):
+                        return
+                    except ssl.SSLEOFError:
+                        return self.handle_close()
+                    except ssl.SSLError:
                         raise
                     except socket.error as err:
                         if err.args[0] == errno.ECONNABORTED:
index ca82490c75fdd7f6e818e3f9d8e4bd297b919f5e..4b6b828baa96b881894f68c235048a57347b7a61 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -341,6 +341,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #11183: Add finer-grained exceptions to the ssl module, so that
+  you don't have to inspect the exception's attributes in the common case.
+
 - Issue #13216: Add cp65001 codec, the Windows UTF-8 (CP_UTF8).
 
 - Issue #13226: Add RTLD_xxx constants to the os module. These constants can be
index 2998605476d28c3139ee4adb07a46c00d07baaf9..dcde4ff2afe0d3c5a7c985cea938ddb1e74be36e 100644 (file)
@@ -99,6 +99,11 @@ static PySocketModule_APIObject PySocketModule;
 
 /* SSL error object */
 static PyObject *PySSLErrorObject;
+static PyObject *PySSLZeroReturnErrorObject;
+static PyObject *PySSLWantReadErrorObject;
+static PyObject *PySSLWantWriteErrorObject;
+static PyObject *PySSLSyscallErrorObject;
+static PyObject *PySSLEOFErrorObject;
 
 #ifdef WITH_THREAD
 
@@ -191,6 +196,7 @@ static PyObject *
 PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
 {
     PyObject *v;
+    PyObject *type = PySSLErrorObject;
     char buf[2048];
     char *errstr;
     int err;
@@ -203,15 +209,18 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
 
         switch (err) {
         case SSL_ERROR_ZERO_RETURN:
-            errstr = "TLS/SSL connection has been closed";
+            errstr = "TLS/SSL connection has been closed (EOF)";
+            type = PySSLZeroReturnErrorObject;
             p = PY_SSL_ERROR_ZERO_RETURN;
             break;
         case SSL_ERROR_WANT_READ:
             errstr = "The operation did not complete (read)";
+            type = PySSLWantReadErrorObject;
             p = PY_SSL_ERROR_WANT_READ;
             break;
         case SSL_ERROR_WANT_WRITE:
             p = PY_SSL_ERROR_WANT_WRITE;
+            type = PySSLWantWriteErrorObject;
             errstr = "The operation did not complete (write)";
             break;
         case SSL_ERROR_WANT_X509_LOOKUP:
@@ -230,6 +239,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
                   = (PySocketSockObject *) PyWeakref_GetObject(obj->Socket);
                 if (ret == 0 || (((PyObject *)s) == Py_None)) {
                     p = PY_SSL_ERROR_EOF;
+                    type = PySSLEOFErrorObject;
                     errstr = "EOF occurred in violation of protocol";
                 } else if (ret == -1) {
                     /* underlying BIO reported an I/O error */
@@ -240,6 +250,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
                     return v;
                 } else { /* possible? */
                     p = PY_SSL_ERROR_SYSCALL;
+                    type = PySSLSyscallErrorObject;
                     errstr = "Some I/O error occurred";
                 }
             } else {
@@ -272,7 +283,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
     ERR_clear_error();
     v = Py_BuildValue("(is)", p, buf);
     if (v != NULL) {
-        PyErr_SetObject(PySSLErrorObject, v);
+        PyErr_SetObject(type, v);
         Py_DECREF(v);
     }
     return NULL;
@@ -2300,6 +2311,23 @@ parse_openssl_version(unsigned long libver,
 PyDoc_STRVAR(SSLError_doc,
 "An error occurred in the SSL implementation.");
 
+PyDoc_STRVAR(SSLZeroReturnError_doc,
+"SSL/TLS session closed cleanly.");
+
+PyDoc_STRVAR(SSLWantReadError_doc,
+"Non-blocking SSL socket needs to read more data\n"
+"before the requested operation can be completed.");
+
+PyDoc_STRVAR(SSLWantWriteError_doc,
+"Non-blocking SSL socket needs to write more data\n"
+"before the requested operation can be completed.");
+
+PyDoc_STRVAR(SSLSyscallError_doc,
+"System error when attempting SSL operation.");
+
+PyDoc_STRVAR(SSLEOFError_doc,
+"SSL/TLS connection terminated abruptly.");
+
 
 PyMODINIT_FUNC
 PyInit__ssl(void)
@@ -2343,7 +2371,33 @@ PyInit__ssl(void)
                                                  NULL);
     if (PySSLErrorObject == NULL)
         return NULL;
-    if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0)
+    PySSLZeroReturnErrorObject = PyErr_NewExceptionWithDoc(
+        "ssl.SSLZeroReturnError", SSLZeroReturnError_doc,
+        PySSLErrorObject, NULL);
+    PySSLWantReadErrorObject = PyErr_NewExceptionWithDoc(
+        "ssl.SSLWantReadError", SSLWantReadError_doc,
+        PySSLErrorObject, NULL);
+    PySSLWantWriteErrorObject = PyErr_NewExceptionWithDoc(
+        "ssl.SSLWantWriteError", SSLWantWriteError_doc,
+        PySSLErrorObject, NULL);
+    PySSLSyscallErrorObject = PyErr_NewExceptionWithDoc(
+        "ssl.SSLSyscallError", SSLSyscallError_doc,
+        PySSLErrorObject, NULL);
+    PySSLEOFErrorObject = PyErr_NewExceptionWithDoc(
+        "ssl.SSLEOFError", SSLEOFError_doc,
+        PySSLErrorObject, NULL);
+    if (PySSLZeroReturnErrorObject == NULL
+        || PySSLWantReadErrorObject == NULL
+        || PySSLWantWriteErrorObject == NULL
+        || PySSLSyscallErrorObject == NULL
+        || PySSLEOFErrorObject == NULL)
+        return NULL;
+    if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0
+        || PyDict_SetItemString(d, "SSLZeroReturnError", PySSLZeroReturnErrorObject) != 0
+        || PyDict_SetItemString(d, "SSLWantReadError", PySSLWantReadErrorObject) != 0
+        || PyDict_SetItemString(d, "SSLWantWriteError", PySSLWantWriteErrorObject) != 0
+        || PyDict_SetItemString(d, "SSLSyscallError", PySSLSyscallErrorObject) != 0
+        || PyDict_SetItemString(d, "SSLEOFError", PySSLEOFErrorObject) != 0)
         return NULL;
     if (PyDict_SetItemString(d, "_SSLContext",
                              (PyObject *)&PySSLContext_Type) != 0)