[Zope3-checkins] SVN: Zope3/trunk/ Fixed issue 546: non-ASCII docstring cause System Error in RootErrorReportingUtility

Dmitry Vasiliev dima at hlabs.spb.ru
Mon Feb 20 08:18:39 EST 2006


Log message for revision 41712:
  Fixed issue 546: non-ASCII docstring cause System Error in RootErrorReportingUtility
  

Changed:
  U   Zope3/trunk/doc/CHANGES.txt
  U   Zope3/trunk/src/zope/app/error/error.py
  U   Zope3/trunk/src/zope/app/error/tests.py

-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt	2006-02-20 11:28:00 UTC (rev 41711)
+++ Zope3/trunk/doc/CHANGES.txt	2006-02-20 13:18:38 UTC (rev 41712)
@@ -41,6 +41,9 @@
 
     Bug Fixes
 
+      - Fixed issue 546: non-ASCII docstring cause
+        System Error in RootErrorReportingUtility.
+
       - Fixed issue 547: return 'add_content' for backward-compatibility.
         Removed 'zope.app.container.browser.adding.ContentAdding'.
 

Modified: Zope3/trunk/src/zope/app/error/error.py
===================================================================
--- Zope3/trunk/src/zope/app/error/error.py	2006-02-20 11:28:00 UTC (rev 41711)
+++ Zope3/trunk/src/zope/app/error/error.py	2006-02-20 13:18:38 UTC (rev 41712)
@@ -53,6 +53,41 @@
 
 logger = logging.getLogger('SiteError')
 
+def getPrintable(value):
+    # A call to unicode(obj) could raise anything at all.
+    # We'll ignore these errors, and print something
+    # useful instead, but also log the error.
+    try:
+        printable = unicode(value)
+    except:
+        logger.exception(
+            "Error in ErrorReportingUtility while getting a unicode"
+            " representation of an object")
+        printable = u"<unprintable %s object>" % type(value).__name__
+        if type(value) is str:
+            try:
+                r = repr(value)
+            except:
+                pass
+            else:
+                try:
+                    printable = unicode(r)
+                except:
+                    pass
+    return printable
+
+def getFormattedException(info, as_html=False):
+    lines = []
+    for line in format_exception(as_html=as_html, *info):
+        line = getPrintable(line)
+        if not line.endswith("\n"):
+            if not as_html:
+                line += "\n"
+            else:
+                line += "<br />\n"
+        lines.append(line)
+    return u"".join(lines)
+
 class ErrorReportingUtility(Persistent, Contained):
     """Error Reporting Utility"""
     implements(IErrorReportingUtility, ILocalErrorReportingUtility)
@@ -72,6 +107,47 @@
             _temp_logs[self._p_oid] = log
         return log
 
+    def _getUsername(self, request):
+        username = None
+
+        principal = getattr(request, "principal", None)
+        if principal is None:
+            return username
+
+        # UnauthenticatedPrincipal does not have getLogin()
+        getLogin = getattr(principal, "getLogin", None)
+        if getLogin is None:
+            login = "unauthenticated"
+        else:
+            try:
+                login = getLogin()
+            except:
+                logger.exception("Error in ErrorReportingUtility while"
+                    " getting login of the principal")
+                login = u"<error getting login>"
+
+        parts = []
+        for part in [
+                login,
+                getattr(principal, "id",
+                    u"<error getting 'principal.id'>"),
+                getattr(principal, "title",
+                    u"<error getting 'principal.title'>"),
+                getattr(principal, "description",
+                    u"<error getting 'principal.description'>")
+                ]:
+            part = getPrintable(part)
+            parts.append(part)
+        username = u", ".join(parts)
+        return username
+
+    def _getRequestAsHTML(self, request):
+        lines = []
+        for key, value in request.items():
+            lines.append(u"%s: %s<br />\n" % (
+                getPrintable(key), getPrintable(value)))
+        return u"".join(lines)
+
     # Exceptions that happen all the time, so we dont need
     # to log them. Eventually this should be configured
     # through-the-web.
@@ -81,18 +157,17 @@
         """
         now = time.time()
         try:
-            tb_text = None
-            tb_html = None
-
-            strtype = str(getattr(info[0], '__name__', info[0]))
+            strtype = unicode(getattr(info[0], '__name__', info[0]))
             if strtype in self._ignored_exceptions:
                 return
 
+            tb_text = None
+            tb_html = None
             if not isinstance(info[2], StringTypes):
-                tb_text = ''.join(format_exception(as_html=0, *info))
-                tb_html = ''.join(format_exception(as_html=1, *info))
+                tb_text = getFormattedException(info)
+                tb_html = getFormattedException(info, True)
             else:
-                tb_text = info[2]
+                tb_text = getPrintable(info[2])
 
             url = None
             username = None
@@ -102,41 +177,10 @@
                 #      just too HTTPRequest-specific.
                 if hasattr(request, 'URL'):
                     url = request.URL
-                try:
-                    # UnauthenticatedPrincipal does not have getLogin()
-                    if hasattr(request.principal, 'getLogin'):
-                        login = request.principal.getLogin()
-                    else:
-                        login = 'unauthenticated'
-                    username = ', '.join([unicode(s) for s in (login,
-                                          request.principal.id,
-                                          request.principal.title,
-                                          request.principal.description
-                                         )])
-                # When there's an unauthorized access, request.principal is
-                # not set, so we get an AttributeError
-                # ??? Is this right? Surely request.principal should be set!
-                # !!! Yes.  Catching AttributeError is correct for the simple
-                # reason that UnauthenticatedUser (which I always use during
-                # coding), has no 'getLogin()' method. However, for some reason
-                # this except does **NOT** catch these errors.
-                except AttributeError:
-                    pass
+                username = self._getUsername(request)
+                req_html = self._getRequestAsHTML(request)
 
-                req_html = ''.join(['%s : %s<br>' % item
-                                    for item in request.items()])
-            try:
-                strv = str(info[1])
-            # A call to str(obj) could raise anything at all.
-            # We'll ignore these errors, and print something
-            # useful instead, but also log the error.
-            except:
-                logger.exception(
-                    'Error in ErrorReportingUtility while getting a str '
-                    'representation of an object')
-                strv = '<unprintable %s object>' % (
-                        str(type(info[1]).__name__)
-                        )
+            strv = getPrintable(info[1])
 
             log = self._getLog()
             entry_id = str(now) + str(random()) # Low chance of collision
@@ -192,7 +236,7 @@
         self.keep_entries = int(keep_entries)
         self.copy_to_zlog = bool(copy_to_zlog)
         self._ignored_exceptions = tuple(
-                [str(e) for e in ignored_exceptions if e]
+                [unicode(e) for e in ignored_exceptions if e]
                 )
 
     def getLogEntries(self):

Modified: Zope3/trunk/src/zope/app/error/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/error/tests.py	2006-02-20 11:28:00 UTC (rev 41711)
+++ Zope3/trunk/src/zope/app/error/tests.py	2006-02-20 13:18:38 UTC (rev 41712)
@@ -22,24 +22,22 @@
 from zope.publisher.tests.httprequest import TestRequest
 from zope.app.testing.placelesssetup import PlacelessSetup
 
-from zope.app.error.error import ErrorReportingUtility
+from zope.app.error.error import ErrorReportingUtility, getFormattedException
 
 class Error(Exception):
 
+    def __init__(self, value):
+        self.value = value
+
     def __str__(self):
-        return u"Error (\u0410)"
+        return self.value
 
-class C1(object):
+def getAnErrorInfo(value=""):
+    try:
+        raise Error(value)
+    except:
+        return sys.exc_info()
 
-    def getAnErrorInfo(self):
-        exc_info = None
-        try:
-            raise Error()
-        except:
-            exc_info = sys.exc_info()
-        return exc_info
-
-
 class ErrorReportingUtilityTests(PlacelessSetup, unittest.TestCase):
 
     def test_checkForEmpryLog(self):
@@ -64,12 +62,12 @@
         # Test for Logging Error.  Create one error and check whether its
         # logged or not.
         errUtility = ErrorReportingUtility()
-        exc_info = C1().getAnErrorInfo()
+        exc_info = getAnErrorInfo()
         errUtility.raising(exc_info)
         getErrLog = errUtility.getLogEntries()
         self.assertEquals(1, len(getErrLog))
 
-        tb_text = ''.join(format_exception(*exc_info, **{'as_html': 0}))
+        tb_text = ''.join(format_exception(as_html=0, *exc_info))
 
         err_id = getErrLog[0]['id']
         self.assertEquals(tb_text,
@@ -88,12 +86,12 @@
         request.setPrincipal(PrincipalStub())
 
         errUtility = ErrorReportingUtility()
-        exc_info = C1().getAnErrorInfo()
+        exc_info = getAnErrorInfo(u"Error (\u0441)")
         errUtility.raising(exc_info, request=request)
         getErrLog = errUtility.getLogEntries()
         self.assertEquals(1, len(getErrLog))
 
-        tb_text = ''.join(format_exception(*exc_info, **{'as_html': 0}))
+        tb_text = ''.join(format_exception(as_html=0, *exc_info))
 
         err_id = getErrLog[0]['id']
         self.assertEquals(tb_text,
@@ -102,7 +100,35 @@
         username = getErrLog[0]['username']
         self.assertEquals(username, u'unauthenticated, \u0441, \u0441, \u0441')
 
+    def test_ErrorLog_nonascii(self):
+        # Emulate a unicode url, it gets encoded to utf-8 before it's passed
+        # to the request. Also add some unicode field to the request's
+        # environment and make the principal's title unicode.
+        request = TestRequest(environ={'PATH_INFO': '/\xd1\x82',
+                                       'SOME_NONASCII': '\xe1'})
+        class PrincipalStub(object):
+            id = '\xe1'
+            title = '\xe1'
+            description = '\xe1'
+        request.setPrincipal(PrincipalStub())
 
+        errUtility = ErrorReportingUtility()
+        exc_info = getAnErrorInfo("Error (\xe1)")
+        errUtility.raising(exc_info, request=request)
+        getErrLog = errUtility.getLogEntries()
+        self.assertEquals(1, len(getErrLog))
+
+        tb_text = getFormattedException(exc_info)
+
+        err_id = getErrLog[0]['id']
+        self.assertEquals(tb_text,
+                          errUtility.getLogEntryById(err_id)['tb_text'])
+
+        username = getErrLog[0]['username']
+        self.assertEquals(username, "unauthenticated,"
+            " '\\xe1', '\\xe1', '\\xe1'")
+
+
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(ErrorReportingUtilityTests),



More information about the Zope3-Checkins mailing list