Blob Blame History Raw
Index: /trunk/trac/web/_fcgi.py
===================================================================
--- /trunk/trac/web/_fcgi.py	(revision 17540)
+++ /trunk/trac/web/_fcgi.py	(revision 17541)
@@ -40,26 +40,16 @@
 __version__ = '$Revision: 2025 $'
 
+import _thread
+import codecs
+import errno
 import io
+import os
+import select
+import signal
+import socket
+import struct
 import sys
-import os
-import signal
-import struct
-import select
-import socket
-import errno
+import threading
 import traceback
-
-try:
-    import thread
-    import threading
-    thread_available = True
-except ImportError:
-    import dummy_thread as thread
-    import dummy_threading as threading
-    thread_available = False
-
-# Apparently 2.3 doesn't define SHUT_WR? Assume it is 1 in this case.
-if not hasattr(socket, 'SHUT_WR'):
-    socket.SHUT_WR = 1
 
 __all__ = ['WSGIServer']
@@ -138,5 +128,5 @@
         self._shrinkThreshold = conn.server.inputStreamShrinkThreshold
 
-        self._buf = ''
+        self._buf = b''
         self._bufList = []
         self._pos = 0 # Current read position.
@@ -160,5 +150,5 @@
     def read(self, n=-1):
         if self._pos == self._avail and self._eof:
-            return ''
+            return b''
         while True:
             if n < 0 or (self._avail - self._pos) < n:
@@ -177,5 +167,5 @@
         # Merge buffer list, if necessary.
         if self._bufList:
-            self._buf += ''.join(self._bufList)
+            self._buf += _bytes_join(self._bufList)
             self._bufList = []
         r = self._buf[self._pos:newPos]
@@ -186,9 +176,9 @@
     def readline(self, length=None):
         if self._pos == self._avail and self._eof:
-            return ''
+            return b''
         while True:
             # Unfortunately, we need to merge the buffer list early.
             if self._bufList:
-                self._buf += ''.join(self._bufList)
+                self._buf += _bytes_join(self._bufList)
                 self._bufList = []
             # Find newline.
@@ -317,4 +307,5 @@
     def write(self, data):
         assert not self.closed
+        assert type(data) is bytes
 
         if not data:
@@ -337,5 +328,5 @@
         # Only need to flush if this OutputStream is actually buffered.
         if self._buffered:
-            data = ''.join(self._bufList)
+            data = _bytes_join(self._bufList)
             self._bufList = []
             self._write(data)
@@ -397,22 +388,22 @@
     are returned.
     """
-    nameLength = ord(s[pos])
-    if nameLength & 128:
-        nameLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
+    namelen = s[pos]
+    if namelen & 128:
+        namelen = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
         pos += 4
     else:
         pos += 1
 
-    valueLength = ord(s[pos])
-    if valueLength & 128:
-        valueLength = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
+    valuelen = s[pos]
+    if valuelen & 128:
+        valuelen = struct.unpack('!L', s[pos:pos+4])[0] & 0x7fffffff
         pos += 4
     else:
         pos += 1
 
-    name = s[pos:pos+nameLength]
-    pos += nameLength
-    value = s[pos:pos+valueLength]
-    pos += valueLength
+    name = str(s[pos:pos+namelen], 'iso-8859-1')
+    pos += namelen
+    value = str(s[pos:pos+valuelen], 'iso-8859-1')
+    pos += valuelen
 
     return pos, (name, value)
@@ -424,17 +415,25 @@
     The encoded string is returned.
     """
-    nameLength = len(name)
-    if nameLength < 128:
-        s = chr(nameLength)
+    name = name.encode('iso-8859-1')
+    value = value.encode('iso-8859-1')
+
+    namelen = len(name)
+    if namelen < 128:
+        namelen = _code2bytes(namelen)
     else:
-        s = struct.pack('!L', nameLength | 0x80000000)
-
-    valueLength = len(value)
-    if valueLength < 128:
-        s += chr(valueLength)
+        namelen = struct.pack('!L', namelen | 0x80000000)
+
+    valuelen = len(value)
+    if valuelen < 128:
+        valuelen = _code2bytes(valuelen)
     else:
-        s += struct.pack('!L', valueLength | 0x80000000)
-
-    return s + name + value
+        valuelen = struct.pack('!L', valuelen | 0x80000000)
+
+    return namelen + valuelen + name + value
+
+_bytes_join = b''.join
+_str_join = ''.join
+_code2bytes = lambda code: b'%c' % code
+_utf8_writer = lambda f: codecs.getwriter('utf-8')(f)
 
 class Record(object):
@@ -450,5 +449,5 @@
         self.contentLength = 0
         self.paddingLength = 0
-        self.contentData = ''
+        self.contentData = b''
 
     @staticmethod
@@ -464,5 +463,5 @@
                 data = sock.recv(length)
             except socket.error as e:
-                if e[0] == errno.EAGAIN:
+                if e.errno == errno.EAGAIN:
                     select.select([sock], [], [])
                     continue
@@ -475,5 +474,5 @@
             recvLen += dataLen
             length -= dataLen
-        return ''.join(dataList), recvLen
+        return _bytes_join(dataList), recvLen
 
     def read(self, sock):
@@ -516,4 +515,5 @@
         Writes data to a socket and does not return until all the data is sent.
         """
+        assert type(data) is bytes
         length = len(data)
         while length:
@@ -521,5 +521,5 @@
                 sent = sock.send(data)
             except socket.error as e:
-                if e[0] == errno.EAGAIN:
+                if e.errno == errno.EAGAIN:
                     select.select([], [sock], [])
                     continue
@@ -545,5 +545,5 @@
             self._sendall(sock, self.contentData)
         if self.paddingLength:
-            self._sendall(sock, '\x00'*self.paddingLength)
+            self._sendall(sock, b'\x00' * self.paddingLength)
 
 class Request(object):
@@ -563,5 +563,6 @@
         self.stdin = inputStreamClass(conn)
         self.stdout = OutputStream(conn, self, FCGI_STDOUT)
-        self.stderr = OutputStream(conn, self, FCGI_STDERR, buffered=True)
+        self.stderr = _utf8_writer(OutputStream(conn, self, FCGI_STDERR,
+                                                buffered=True))
         self.data = inputStreamClass(conn)
 
@@ -585,5 +586,5 @@
             self._end(appStatus, protocolStatus)
         except socket.error as e:
-            if e[0] != errno.EPIPE:
+            if e.errno != errno.EPIPE:
                 raise
 
@@ -645,6 +646,6 @@
         try:
             while True:
-                r, w, e = select.select([self._sock], [], [])
-                if not r or not self._sock.recv(1024):
+                rlist, wlist, xlist = select.select([self._sock], [], [])
+                if not rlist or not self._sock.recv(1024):
                     break
         except:
@@ -660,6 +661,6 @@
             except EOFError:
                 break
-            except (select.error, socket.error) as e:
-                if e[0] == errno.EBADF: # Socket was closed by Request.
+            except select.error as e:
+                if e.errno == errno.EBADF: # Socket was closed by Request.
                     break
                 raise
@@ -675,10 +676,10 @@
         while self._keepGoing:
             try:
-                r, w, e = select.select([self._sock], [], [], 1.0)
+                rlist, wlist, xlist = select.select([self._sock], [], [], 1.0)
             except ValueError:
                 # Sigh. ValueError gets thrown sometimes when passing select
                 # a closed socket.
                 raise EOFError
-            if r: break
+            if rlist: break
         if not self._keepGoing:
             return
@@ -876,5 +877,5 @@
 
     def _start_request(self, req):
-        thread.start_new_thread(req.run, ())
+        _thread.start_new_thread(req.run, ())
 
     def _do_params(self, inrec):
@@ -948,31 +949,22 @@
             self.handler = handler
         self.maxwrite = maxwrite
-        if thread_available:
-            try:
-                import resource
-                # Attempt to glean the maximum number of connections
-                # from the OS.
-                maxConns = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
-            except (ImportError, AttributeError):
-                maxConns = 100 # Just some made up number.
-            maxReqs = maxConns
-            if multiplexed:
-                self._connectionClass = MultiplexedConnection
-                maxReqs *= 5 # Another made up number.
-            else:
-                self._connectionClass = Connection
-            self.capability = {
-                FCGI_MAX_CONNS: maxConns,
-                FCGI_MAX_REQS: maxReqs,
-                FCGI_MPXS_CONNS: multiplexed and 1 or 0
-                }
+        try:
+            import resource
+            # Attempt to glean the maximum number of connections
+            # from the OS.
+            maxConns = resource.getrlimit(resource.RLIMIT_NOFILE)[0]
+        except (ImportError, AttributeError):
+            maxConns = 100 # Just some made up number.
+        maxReqs = maxConns
+        if multiplexed:
+            self._connectionClass = MultiplexedConnection
+            maxReqs *= 5 # Another made up number.
         else:
             self._connectionClass = Connection
-            self.capability = {
-                # If threads aren't available, these are pretty much correct.
-                FCGI_MAX_CONNS: 1,
-                FCGI_MAX_REQS: 1,
-                FCGI_MPXS_CONNS: 0
-                }
+        self.capability = {
+            FCGI_MAX_CONNS: maxConns,
+            FCGI_MAX_REQS: maxReqs,
+            FCGI_MPXS_CONNS: 1 if multiplexed else 0,
+        }
         self._bindAddress = bindAddress
         self._umask = umask
@@ -987,8 +979,8 @@
                 sock.getpeername()
             except socket.error as e:
-                if e[0] == errno.ENOTSOCK:
+                if e.errno == errno.ENOTSOCK:
                     # Not a socket, assume CGI context.
                     isFCGI = False
-                elif e[0] != errno.ENOTCONN:
+                elif e.errno != errno.ENOTCONN:
                     raise
 
@@ -1072,15 +1064,15 @@
         while self._keepGoing:
             try:
-                r, w, e = select.select([sock], [], [], timeout)
+                rlist, wlist, xlist = select.select([sock], [], [], timeout)
             except select.error as e:
-                if e[0] == errno.EINTR:
+                if e.errno == errno.EINTR:
                     continue
                 raise
 
-            if r:
+            if rlist:
                 try:
                     clientSock, addr = sock.accept()
                 except socket.error as e:
-                    if e[0] in (errno.EINTR, errno.EAGAIN):
+                    if e.errno in (errno.EINTR, errno.EAGAIN):
                         continue
                     raise
@@ -1094,5 +1086,5 @@
                 # messages (either in a new thread or this thread).
                 conn = self._connectionClass(clientSock, addr, self)
-                thread.start_new_thread(conn.run, ())
+                _thread.start_new_thread(conn.run, ())
 
             self._mainloopPeriodic()
@@ -1134,7 +1126,8 @@
         should be overridden.
         """
-        import cgitb
-        req.stdout.write('Content-Type: text/html\r\n\r\n' +
-                         cgitb.html(sys.exc_info()))
+        out = _utf8_writer(req.stdout)
+        out.write('Content-Type: text/plain; charset=utf-8\r\n\r\n')
+        traceback.print_exc(file=out)
+        out.flush()
 
 class WSGIServer(Server):
@@ -1164,5 +1157,5 @@
 
         # Used to force single-threadedness
-        self._app_lock = thread.allocate_lock()
+        self._app_lock = _thread.allocate_lock()
 
     def handler(self, req):
@@ -1175,5 +1168,5 @@
         environ.update(self.environ)
 
-        environ['wsgi.version'] = (1,0)
+        environ['wsgi.version'] = (1, 0)
         environ['wsgi.input'] = req.stdin
         if self._bindAddress is None:
@@ -1183,5 +1176,5 @@
         environ['wsgi.errors'] = stderr
         environ['wsgi.multithread'] = not isinstance(req, CGIRequest) and \
-                                      thread_available and self.multithreaded
+                                      self.multithreaded
         # Rationale for the following: If started by the web server
         # (self._bindAddress is None) in either FastCGI or CGI mode, the
@@ -1207,26 +1200,15 @@
 
         def write(data):
-            assert type(data) is str, 'write() argument must be string'
+            assert type(data) is bytes, 'write() argument must be bytes'
             assert headers_set, 'write() before start_response()'
 
             if not headers_sent:
-                status, responseHeaders = headers_sent[:] = headers_set
-                found = False
-                for header,value in responseHeaders:
-                    if header.lower() == 'content-length':
-                        found = True
-                        break
-                if not found and result is not None:
-                    try:
-                        if len(result) == 1:
-                            responseHeaders.append(('Content-Length',
-                                                    str(len(data))))
-                    except:
-                        pass
-                s = 'Status: %s\r\n' % status
-                for header in responseHeaders:
-                    s += '%s: %s\r\n' % header
-                s += '\r\n'
-                req.stdout.write(s)
+                status, headers = headers_sent[:] = headers_set
+                def generator():
+                    yield 'Status: %s\r\n' % status
+                    for header in headers:
+                        yield '%s: %s\r\n' % header
+                    yield '\r\n'
+                req.stdout.write(_str_join(generator()).encode('iso-8859-1'))
 
             req.stdout.write(data)
@@ -1266,11 +1248,11 @@
                             write(data)
                     if not headers_sent:
-                        write('') # in case body was empty
+                        write(b'') # in case body was empty
                 finally:
                     if hasattr(result, 'close'):
                         result.close()
             except socket.error as e:
-                if e[0] != errno.EPIPE:
-                    raise # Don't let EPIPE propagate beyond server
+                if e.errno != errno.EPIPE:
+                    raise  # Don't let EPIPE propagate beyond server
         finally:
             if not self.multithreaded:
Index: /trunk/trac/web/fcgi_frontend.py
===================================================================
--- /trunk/trac/web/fcgi_frontend.py	(revision 17540)
+++ /trunk/trac/web/fcgi_frontend.py	(revision 17541)
@@ -27,5 +27,4 @@
     use_flup = False
 
-
 class FlupMiddleware(object):
     """Flup doesn't URL unquote the PATH_INFO, so we need to do it."""
@@ -43,8 +42,9 @@
     try:
         from flup.server.fcgi import WSGIServer
+    except ImportError:
+        use_flup = False
+    else:
         params['maxThreads'] = 15
         dispatch_request = FlupMiddleware(dispatch_request)
-    except ImportError:
-        use_flup = False
 
 if not use_flup:
Index: /trunk/trac/web/templates/deploy_trac.fcgi
===================================================================
--- /trunk/trac/web/templates/deploy_trac.fcgi	(revision 17540)
+++ /trunk/trac/web/templates/deploy_trac.fcgi	(revision 17541)
@@ -34,14 +34,12 @@
     raise
 except Exception as e:
-    print("Content-Type: text/plain\r\n\r\n", end=' ')
+    print("Content-Type: text/plain", end="\r\n")
+    print("", end="\r\n")
     print("Oops...")
-    print()
+    print("")
     print("Trac detected an internal error:")
-    print()
+    print("")
     print(e)
-    print()
+    print("", flush=True)
     import traceback
-    import io
-    tb = io.Bytes()
-    traceback.print_exc(file=tb)
-    print(tb.getvalue())
+    traceback.print_exc(file=sys.stdout)