Robert Brewer avatar Robert Brewer committed 12f7785

Fixed #1087 (PEP-3333 compliance: Unicode response headers). Also some touch-ups to prevent refleaks.

Comments (0)

Files changed (8)

cherrypy/_cperror.py

 
 def format_exc(exc=None):
     """Return exc (or sys.exc_info if None), formatted."""
-    if exc is None:
-        exc = _exc_info()
-    if exc == (None, None, None):
-        return ""
-    import traceback
-    return "".join(traceback.format_exception(*exc))
+    try:
+        if exc is None:
+            exc = _exc_info()
+        if exc == (None, None, None):
+            return ""
+        import traceback
+        return "".join(traceback.format_exception(*exc))
+    finally:
+        del exc
 
 def bare_error(extrabody=None):
     """Produce status, headers, body for a critical error.

cherrypy/_cpwsgi.py

 import sys as _sys
 
 import cherrypy as _cherrypy
-from cherrypy._cpcompat import BytesIO, ntob, ntou, py3k, unicodestr
+from cherrypy._cpcompat import BytesIO, bytestr, ntob, ntou, py3k, unicodestr
 from cherrypy import _cperror
 from cherrypy.lib import httputil
 
             if not _cherrypy.request.show_tracebacks:
                 tb = ""
             s, h, b = _cperror.bare_error(tb)
+            if py3k:
+                # What fun.
+                s = s.decode('ISO-8859-1')
+                h = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
+                     for k, v in h]
             if self.started_response:
                 # Empty our iterable (so future calls raise StopIteration)
                 self.iter_response = iter([])
     """WSGI response iterable for CherryPy applications."""
     
     def __init__(self, environ, start_response, cpapp):
-        if not py3k:
-            if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
-                environ = downgrade_wsgi_ux_to_1x(environ)
-        self.environ = environ
         self.cpapp = cpapp
         try:
+            if not py3k:
+                if environ.get(ntou('wsgi.version')) == (ntou('u'), 0):
+                    environ = downgrade_wsgi_ux_to_1x(environ)
+            self.environ = environ
             self.run()
-        except:
-            self.close()
-            raise
-        r = _cherrypy.serving.response
-        self.iter_response = iter(r.body)
-        try:
-            self.write = start_response(r.output_status, r.header_list)
+
+            r = _cherrypy.serving.response
+
+            outstatus = r.output_status
+            if not isinstance(outstatus, bytestr):
+                raise TypeError("response.output_status is not a byte string.")
+            
+            outheaders = []
+            for k, v in r.header_list:
+                if not isinstance(k, bytestr):
+                    raise TypeError("response.header_list key %r is not a byte string." % k)
+                if not isinstance(v, bytestr):
+                    raise TypeError("response.header_list value %r is not a byte string." % v)
+                outheaders.append((k, v))
+            
+            if py3k:
+                # According to PEP 3333, when using Python 3, the response status
+                # and headers must be bytes masquerading as unicode; that is, they
+                # must be of type "str" but are restricted to code points in the
+                # "latin-1" set.
+                outstatus = outstatus.decode('ISO-8859-1')
+                outheaders = [(k.decode('ISO-8859-1'), v.decode('ISO-8859-1'))
+                              for k, v in outheaders]
+
+            self.iter_response = iter(r.body)
+            self.write = start_response(outstatus, outheaders)
         except:
             self.close()
             raise

cherrypy/process/wspbus.py

     
     def handle_exception(self):
         """Append the current exception to self."""
-        self._exceptions.append(sys.exc_info())
+        self._exceptions.append(sys.exc_info()[1])
     
     def get_instances(self):
         """Return a list of seen exception instances."""
-        return [instance for cls, instance, traceback in self._exceptions]
+        return self._exceptions[:]
     
     def __str__(self):
         exception_strings = map(repr, self.get_instances())
     def log(self, msg="", level=20, traceback=False):
         """Log the given message. Append the last traceback if requested."""
         if traceback:
-            exc = sys.exc_info()
-            msg += "\n" + "".join(_traceback.format_exception(*exc))
+            msg += "\n" + "".join(_traceback.format_exception(*sys.exc_info()))
         self.publish('log', msg, level)
 
 bus = Bus()

cherrypy/test/helper.py

         if self.do_gc_test:
             self.getPage("/gc/stats")
             self.assertBody("Statistics:")
+    # Tell nose to run this last in each class
+    test_gc.compat_co_firstlineno = float('inf')
     
     def prefix(self):
         return self.script_name.rstrip("/")

cherrypy/test/test_core.py

     def test_start_response_error(self):
         self.getPage("/start_response_error")
         self.assertStatus(500)
-        self.assertInBody("TypeError: WSGI response header key 2 is not a byte string.")
+        self.assertInBody("TypeError: response.header_list key 2 is not a byte string.")
+

cherrypy/test/test_wsgiapps.py

         import cherrypy
         
         def test_app(environ, start_response):
-            status = ntob('200 OK')
-            response_headers = [(ntob('Content-type'), ntob('text/plain'))]
+            status = '200 OK'
+            response_headers = [('Content-type', 'text/plain')]
             start_response(status, response_headers)
             output = ['Hello, world!\n',
                       'This is a wsgi app running within CherryPy!\n\n']
             return [ntob(x, 'utf-8') for x in output]
         
         def test_empty_string_app(environ, start_response):
-            status = ntob('200 OK')
-            response_headers = [(ntob('Content-type'), ntob('text/plain'))]
+            status = '200 OK'
+            response_headers = [('Content-type', 'text/plain')]
             start_response(status, response_headers)
             return [ntob('Hello'), ntob(''), ntob(' '), ntob(''), ntob('world')]
         

cherrypy/wsgiserver/wsgiserver2.py

         
         self.req.status = status
         for k, v in headers:
-            if not isinstance(k, bytestr):
-                raise TypeError("WSGI response header key %r is not a byte string." % k)
-            if not isinstance(v, bytestr):
-                raise TypeError("WSGI response header value %r is not a byte string." % v)
+            if not isinstance(k, str):
+                raise TypeError("WSGI response header key %r is not of type str." % k)
+            if not isinstance(v, str):
+                raise TypeError("WSGI response header value %r is not of type str." % v)
             if k.lower() == 'content-length':
                 self.remaining_bytes_out = int(v)
         self.req.outheaders.extend(headers)

cherrypy/wsgiserver/wsgiserver3.py

                 raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
             finally:
                 exc_info = None
-        
-        self.req.status = status
+
+        # According to PEP 3333, when using Python 3, the response status
+        # and headers must be bytes masquerading as unicode; that is, they
+        # must be of type "str" but are restricted to code points in the
+        # "latin-1" set.
+        if not isinstance(status, str):
+            raise TypeError("WSGI response status is not of type str.")
+        self.req.status = status.encode('ISO-8859-1')
+
         for k, v in headers:
-            if not isinstance(k, bytestr):
-                raise TypeError("WSGI response header key %r is not a byte string." % k)
-            if not isinstance(v, bytestr):
-                raise TypeError("WSGI response header value %r is not a byte string." % v)
-            if k.lower() == b'content-length':
+            if not isinstance(k, str):
+                raise TypeError("WSGI response header key %r is not of type str." % k)
+            if not isinstance(v, str):
+                raise TypeError("WSGI response header value %r is not of type str." % v)
+            if k.lower() == 'content-length':
                 self.remaining_bytes_out = int(v)
-        self.req.outheaders.extend(headers)
+            self.req.outheaders.append((k.encode('ISO-8859-1'), v.encode('ISO-8859-1')))
         
         return self.write
     
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.