r47338 - Apply twcgi-py3-8009-3.patch.

adiroiban-TA+aISz0psMTMxyoc4vAAJOcrHinNvQL0E9HWUfgJXw@public.gmane.org Tue, 3 May 2016 01:50:12 -0600 (MDT)
Newsgroups gmane.comp.python.twisted.commits
Message-ID <[email protected]>
Author: adiroiban
Date: Tue May  3 01:50:04 2016
New Revision: 47338

Modified:
   branches/twcgi-py3-8009-3/twisted/web/test/test_cgi.py
   branches/twcgi-py3-8009-3/twisted/web/twcgi.py

Log:
Apply twcgi-py3-8009-3.patch.

Modified: branches/twcgi-py3-8009-3/twisted/web/test/test_cgi.py
==============================================================================
--- branches/twcgi-py3-8009-3/twisted/web/test/test_cgi.py	(original)
+++ branches/twcgi-py3-8009-3/twisted/web/test/test_cgi.py	Tue May  3 01:50:04 2016
@@ -10,35 +10,36 @@
 from twisted.trial import unittest
 from twisted.internet import reactor, interfaces, error
 from twisted.python import util, failure, log
+from twisted.python.compat import _PY3
 from twisted.web.http import NOT_FOUND, INTERNAL_SERVER_ERROR
 from twisted.web import client, twcgi, server, resource
 from twisted.web.test._util import _render
 from twisted.web.test.test_web import DummyRequest
 
 DUMMY_CGI = '''\
-print "Header: OK"
-print
-print "cgi output"
+print("Header: OK")
+print("")
+print("cgi output")
 '''
 
 DUAL_HEADER_CGI = '''\
-print "Header: spam"
-print "Header: eggs"
-print
-print "cgi output"
+print("Header: spam")
+print("Header: eggs")
+print("")
+print("cgi output")
 '''
 
 BROKEN_HEADER_CGI = '''\
-print "XYZ"
-print
-print "cgi output"
+print("XYZ")
+print("")
+print("cgi output")
 '''
 
 SPECIAL_HEADER_CGI = '''\
-print "Server: monkeys"
-print "Date: last year"
-print
-print "cgi output"
+print("Server: monkeys")
+print("Date: last year")
+print("")
+print("cgi output")
 '''
 
 READINPUT_CGI = '''\
@@ -49,9 +50,9 @@
 
 body_length = int(os.environ.get('CONTENT_LENGTH',0))
 indata = sys.stdin.read(body_length)
-print "Header: OK"
-print
-print "readinput ok"
+print("Header: OK")
+print("")
+print("readinput ok")
 '''
 
 READALLINPUT_CGI = '''\
@@ -62,15 +63,15 @@
 import sys
 
 indata = sys.stdin.read()
-print "Header: OK"
-print
-print "readallinput ok"
+print("Header: OK")
+print("")
+print("readallinput ok")
 '''
 
 NO_DUPLICATE_CONTENT_TYPE_HEADER_CGI = '''\
-print "content-type: text/cgi-duplicate-test"
-print
-print "cgi output"
+print("content-type: text/cgi-duplicate-test")
+print("")
+print("cgi output")
 '''
 
 class PythonScript(twcgi.FilteredScript):
@@ -87,7 +88,7 @@
     def startServer(self, cgi):
         root = resource.Resource()
         cgipath = util.sibpath(__file__, cgi)
-        root.putChild("cgi", PythonScript(cgipath))
+        root.putChild(b"cgi", PythonScript(cgipath))
         site = server.Site(root)
         self.p = reactor.listenTCP(0, site)
         return self.p.getHost().port
@@ -99,9 +100,10 @@
 
     def writeCGI(self, source):
         cgiFilename = os.path.abspath(self.mktemp())
-        cgiFile = file(cgiFilename, 'wt')
-        cgiFile.write(source)
-        cgiFile.close()
+
+        with open(cgiFilename, 'wt') as fh:
+            fh.write(source)
+
         return cgiFilename
 
 
@@ -109,13 +111,14 @@
         cgiFilename = self.writeCGI(DUMMY_CGI)
 
         portnum = self.startServer(cgiFilename)
-        d = client.getPage("http://localhost:%d/cgi" % portnum)
+        url = "http://localhost:%d/cgi" % (portnum,)
+        d = client.getPage(url.encode("ascii"))
         d.addCallback(self._testCGI_1)
         return d
 
 
     def _testCGI_1(self, res):
-        self.assertEqual(res, "cgi output" + os.linesep)
+        self.assertEqual(res, ("cgi output" + os.linesep).encode("ascii"))
 
 
     def test_protectedServerAndDate(self):
@@ -127,11 +130,11 @@
 
         portnum = self.startServer(cgiFilename)
         url = "http://localhost:%d/cgi" % (portnum,)
-        factory = client.HTTPClientFactory(url)
+        factory = client.HTTPClientFactory(url.encode("ascii"))
         reactor.connectTCP('localhost', portnum, factory)
         def checkResponse(ignored):
-            self.assertNotIn('monkeys', factory.response_headers['server'])
-            self.assertNotIn('last year', factory.response_headers['date'])
+            self.assertNotIn('monkeys', factory.response_headers[b'server'])
+            self.assertNotIn('last year', factory.response_headers[b'date'])
         factory.deferred.addCallback(checkResponse)
         return factory.deferred
 
@@ -145,11 +148,11 @@
 
         portnum = self.startServer(cgiFilename)
         url = "http://localhost:%d/cgi" % (portnum,)
-        factory = client.HTTPClientFactory(url)
+        factory = client.HTTPClientFactory(url.encode("ascii"))
         reactor.connectTCP('localhost', portnum, factory)
         def checkResponse(ignored):
             self.assertEqual(
-                factory.response_headers['content-type'], ['text/cgi-duplicate-test'])
+                factory.response_headers[b'content-type'], [b'text/cgi-duplicate-test'])
         factory.deferred.addCallback(checkResponse)
         return factory.deferred
 
@@ -163,11 +166,11 @@
 
         portnum = self.startServer(cgiFilename)
         url = "http://localhost:%d/cgi" % (portnum,)
-        factory = client.HTTPClientFactory(url)
+        factory = client.HTTPClientFactory(url.encode("ascii"))
         reactor.connectTCP('localhost', portnum, factory)
         def checkResponse(ignored):
             self.assertEqual(
-                factory.response_headers['header'], ['spam', 'eggs'])
+                factory.response_headers[b'header'], [b'spam', b'eggs'])
         factory.deferred.addCallback(checkResponse)
         return factory.deferred
 
@@ -180,7 +183,7 @@
 
         portnum = self.startServer(cgiFilename)
         url = "http://localhost:%d/cgi" % (portnum,)
-        factory = client.HTTPClientFactory(url)
+        factory = client.HTTPClientFactory(url.encode("ascii"))
         reactor.connectTCP('localhost', portnum, factory)
         loggedMessages = []
 
@@ -191,8 +194,12 @@
         self.addCleanup(log.removeObserver, addMessage)
 
         def checkResponse(ignored):
-            self.assertEqual(loggedMessages[0],
-                             "ignoring malformed CGI header: 'XYZ'")
+            if _PY3:
+                self.assertEqual(loggedMessages[0],
+                                "ignoring malformed CGI header: b'XYZ'")
+            else:
+                self.assertEqual(loggedMessages[0],
+                                "ignoring malformed CGI header: 'XYZ'")
 
         factory.deferred.addCallback(checkResponse)
         return factory.deferred
@@ -200,50 +207,57 @@
 
     def testReadEmptyInput(self):
         cgiFilename = os.path.abspath(self.mktemp())
-        cgiFile = file(cgiFilename, 'wt')
-        cgiFile.write(READINPUT_CGI)
-        cgiFile.close()
+        with open(cgiFilename, 'wt') as fh:
+            fh.write(READINPUT_CGI)
 
         portnum = self.startServer(cgiFilename)
-        d = client.getPage("http://localhost:%d/cgi" % portnum)
+        url = "http://localhost:%d/cgi" % (portnum,)
+        d = client.getPage(url.encode("ascii"))
         d.addCallback(self._testReadEmptyInput_1)
         return d
     testReadEmptyInput.timeout = 5
+
+
     def _testReadEmptyInput_1(self, res):
-        self.assertEqual(res, "readinput ok%s" % os.linesep)
+        self.assertEqual(res, ("readinput ok%s" % os.linesep).encode("ascii"))
 
     def testReadInput(self):
         cgiFilename = os.path.abspath(self.mktemp())
-        cgiFile = file(cgiFilename, 'wt')
-        cgiFile.write(READINPUT_CGI)
-        cgiFile.close()
+        with open(cgiFilename, 'wt') as fh:
+            fh.write(READINPUT_CGI)
 
         portnum = self.startServer(cgiFilename)
-        d = client.getPage("http://localhost:%d/cgi" % portnum,
-                           method="POST",
-                           postdata="Here is your stdin")
+        url = "http://localhost:%d/cgi" % (portnum,)
+        d = client.getPage(url.encode("ascii"),
+                           method=b"POST",
+                           postdata=b"Here is your stdin")
         d.addCallback(self._testReadInput_1)
         return d
     testReadInput.timeout = 5
+
+
     def _testReadInput_1(self, res):
-        self.assertEqual(res, "readinput ok%s" % os.linesep)
+        self.assertEqual(res, ("readinput ok%s" % os.linesep).encode("ascii"))
 
 
     def testReadAllInput(self):
         cgiFilename = os.path.abspath(self.mktemp())
-        cgiFile = file(cgiFilename, 'wt')
-        cgiFile.write(READALLINPUT_CGI)
-        cgiFile.close()
+        with open(cgiFilename, 'wt') as fh:
+            fh.write(READALLINPUT_CGI)
 
         portnum = self.startServer(cgiFilename)
-        d = client.getPage("http://localhost:%d/cgi" % portnum,
-                           method="POST",
-                           postdata="Here is your stdin")
+        url = "http://localhost:%d/cgi" % (portnum,)
+        d = client.getPage(url.encode("ascii"),
+                           method=b"POST",
+                           postdata=b"Here is your stdin")
         d.addCallback(self._testReadAllInput_1)
         return d
     testReadAllInput.timeout = 5
+
+
     def _testReadAllInput_1(self, res):
-        self.assertEqual(res, "readallinput ok%s" % os.linesep)
+        self.assertEqual(res,
+                         ("readallinput ok%s" % os.linesep).encode("ascii"))
 
 
     def test_useReactorArgument(self):

Modified: branches/twcgi-py3-8009-3/twisted/web/twcgi.py
==============================================================================
--- branches/twcgi-py3-8009-3/twisted/web/twcgi.py	(original)
+++ branches/twcgi-py3-8009-3/twisted/web/twcgi.py	Tue May  3 01:50:04 2016
@@ -14,10 +14,21 @@
 # Twisted Imports
 from twisted.web import http
 from twisted.internet import protocol
-from twisted.spread import pb
 from twisted.python import log, filepath
 from twisted.web import resource, server, static
 
+# t.spread.pb.Viewable is a subclass to CGIProcessProtocol but t.spread.pb is
+# not ported to Python 3.x yet. We'll just try to import it anyway and:
+# - in Python 2.x it should just work
+# - in 3.x, SyntaxError will be raised so we'll assign `object` to it;
+#   CGIProcessProtocol won't be Viewable.
+#
+# See https://twistedmatrix.com/trac/ticket/8009 .
+try:
+    from twisted.spread.pb import Viewable
+except SyntaxError:
+    Viewable = object
+
 
 class CGIDirectory(resource.Resource, filepath.FilePath):
     def __init__(self, pathname):
@@ -70,11 +81,14 @@
         I will set up the usual slew of environment variables, then spin off a
         process.
 
+        Headers from HTTP request will be propagated to process' environment;
+        their names will be decoded using 'iso-8859-1'.
+
         @type request: L{twisted.web.http.Request}
         @param request: An HTTP request.
         """
-        script_name = "/" + "/".join(request.prepath)
-        serverName = request.getRequestHostname().split(':')[0]
+        script_name = b"/" + b"/".join(request.prepath)
+        serverName = request.getRequestHostname().split(b':')[0]
         env = {"SERVER_SOFTWARE":   server.version,
                "SERVER_NAME":       serverName,
                "GATEWAY_INTERFACE": "CGI/1.1",
@@ -102,7 +116,7 @@
             env['CONTENT_LENGTH'] = str(length)
 
         try:
-            qindex = request.uri.index('?')
+            qindex = request.uri.index(b'?')
         except ValueError:
             env['QUERY_STRING'] = ''
             qargs = []
@@ -115,7 +129,7 @@
 
         # Propagate HTTP headers
         for title, header in request.getAllHeaders().items():
-            envname = title.replace('-', '_').upper()
+            envname = title.decode('iso-8859-1').replace('-', '_').upper()
             if title not in ('content-type', 'content-length'):
                 envname = "HTTP_" + envname
             env[envname] = header
@@ -125,6 +139,7 @@
                 env[key] = value
         # And they're off!
         self.runProcess(env, request, qargs)
+
         return server.NOT_DONE_YET
 
 
@@ -194,11 +209,11 @@
 
 
 
-class CGIProcessProtocol(protocol.ProcessProtocol, pb.Viewable):
+class CGIProcessProtocol(protocol.ProcessProtocol, Viewable):
     handling_headers = 1
     headers_written = 0
-    headertext = ''
-    errortext = ''
+    headertext = b''
+    errortext = b''
 
     # Remotely relay producer interface.
 
@@ -243,7 +258,7 @@
         if self.handling_headers:
             text = self.headertext + output
             headerEnds = []
-            for delimiter in '\n\n','\r\n\r\n','\r\r', '\n\r\n':
+            for delimiter in b'\n\n', b'\r\n\r\n', b'\r\r', b'\n\r\n':
                 headerend = text.find(delimiter)
                 if headerend != -1:
                     headerEnds.append((headerend, delimiter))
@@ -260,7 +275,7 @@
                 linebreak = delimiter[:len(delimiter)//2]
                 headers = self.headertext.split(linebreak)
                 for header in headers:
-                    br = header.find(': ')
+                    br = header.find(b': ')
                     if br == -1:
                         log.msg(
                             format='ignoring malformed CGI header: %(header)r',