~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/web/twcgi.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: twisted.web.test.test_cgi -*-
 
2
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
 
3
# See LICENSE for details.
 
4
 
 
5
 
 
6
"""
 
7
I hold resource classes and helper classes that deal with CGI scripts.
 
8
"""
 
9
 
 
10
# System Imports
 
11
import string
 
12
import os
 
13
import sys
 
14
import urllib
 
15
 
 
16
# Twisted Imports
 
17
from twisted.web import http
 
18
from twisted.internet import reactor, protocol
 
19
from twisted.spread import pb
 
20
from twisted.python import log, filepath
 
21
from twisted.web import resource, server, static
 
22
 
 
23
 
 
24
class CGIDirectory(resource.Resource, filepath.FilePath):
 
25
    def __init__(self, pathname):
 
26
        resource.Resource.__init__(self)
 
27
        filepath.FilePath.__init__(self, pathname)
 
28
 
 
29
    def getChild(self, path, request):
 
30
        fnp = self.child(path)
 
31
        if not fnp.exists():
 
32
            return static.File.childNotFound
 
33
        elif fnp.isdir():
 
34
            return CGIDirectory(fnp.path)
 
35
        else:
 
36
            return CGIScript(fnp.path)
 
37
        return resource.NoResource()
 
38
 
 
39
    def render(self, request):
 
40
        notFound = resource.NoResource(
 
41
            "CGI directories do not support directory listing.")
 
42
        return notFound.render(request)
 
43
 
 
44
class CGIScript(resource.Resource):
 
45
    """I represent a CGI script.
 
46
 
 
47
    My implementation is complex due to the fact that it requires asynchronous
 
48
    IPC with an external process with an unpleasant protocol.
 
49
    """
 
50
    isLeaf = 1
 
51
    def __init__(self, filename, registry=None):
 
52
        """Initialize, with the name of a CGI script file.
 
53
        """
 
54
        self.filename = filename
 
55
 
 
56
    def render(self, request):
 
57
        """Do various things to conform to the CGI specification.
 
58
 
 
59
        I will set up the usual slew of environment variables, then spin off a
 
60
        process.
 
61
        """
 
62
        script_name = "/"+string.join(request.prepath, '/')
 
63
        python_path = string.join(sys.path, os.pathsep)
 
64
        serverName = string.split(request.getRequestHostname(), ':')[0]
 
65
        env = {"SERVER_SOFTWARE":   server.version,
 
66
               "SERVER_NAME":       serverName,
 
67
               "GATEWAY_INTERFACE": "CGI/1.1",
 
68
               "SERVER_PROTOCOL":   request.clientproto,
 
69
               "SERVER_PORT":       str(request.getHost().port),
 
70
               "REQUEST_METHOD":    request.method,
 
71
               "SCRIPT_NAME":       script_name, # XXX
 
72
               "SCRIPT_FILENAME":   self.filename,
 
73
               "REQUEST_URI":       request.uri,
 
74
        }
 
75
 
 
76
        client = request.getClient()
 
77
        if client is not None:
 
78
            env['REMOTE_HOST'] = client
 
79
        ip = request.getClientIP()
 
80
        if ip is not None:
 
81
            env['REMOTE_ADDR'] = ip
 
82
        pp = request.postpath
 
83
        if pp:
 
84
            env["PATH_INFO"] = "/"+string.join(pp, '/')
 
85
 
 
86
        if hasattr(request, "content"):
 
87
            # request.content is either a StringIO or a TemporaryFile, and
 
88
            # the file pointer is sitting at the beginning (seek(0,0))
 
89
            request.content.seek(0,2)
 
90
            length = request.content.tell()
 
91
            request.content.seek(0,0)
 
92
            env['CONTENT_LENGTH'] = str(length)
 
93
 
 
94
        qindex = string.find(request.uri, '?')
 
95
        if qindex != -1:
 
96
            qs = env['QUERY_STRING'] = request.uri[qindex+1:]
 
97
            if '=' in qs:
 
98
                qargs = []
 
99
            else:
 
100
                qargs = [urllib.unquote(x) for x in qs.split('+')]
 
101
        else:
 
102
            env['QUERY_STRING'] = ''
 
103
            qargs = []
 
104
 
 
105
        # Propogate HTTP headers
 
106
        for title, header in request.getAllHeaders().items():
 
107
            envname = string.upper(string.replace(title, '-', '_'))
 
108
            if title not in ('content-type', 'content-length'):
 
109
                envname = "HTTP_" + envname
 
110
            env[envname] = header
 
111
        # Propogate our environment
 
112
        for key, value in os.environ.items():
 
113
            if not env.has_key(key):
 
114
                env[key] = value
 
115
        # And they're off!
 
116
        self.runProcess(env, request, qargs)
 
117
        return server.NOT_DONE_YET
 
118
 
 
119
    def runProcess(self, env, request, qargs=[]):
 
120
        p = CGIProcessProtocol(request)
 
121
        reactor.spawnProcess(p, self.filename, [self.filename]+qargs, env, os.path.dirname(self.filename))
 
122
 
 
123
 
 
124
class FilteredScript(CGIScript):
 
125
    """I am a special version of a CGI script, that uses a specific executable.
 
126
 
 
127
    This is useful for interfacing with other scripting languages that adhere
 
128
    to the CGI standard (cf. PHPScript).  My 'filter' attribute specifies what
 
129
    executable to run, and my 'filename' init parameter describes which script
 
130
    to pass to the first argument of that script.
 
131
    """
 
132
 
 
133
    filter = '/usr/bin/cat'
 
134
 
 
135
    def runProcess(self, env, request, qargs=[]):
 
136
        p = CGIProcessProtocol(request)
 
137
        reactor.spawnProcess(p, self.filter, [self.filter, self.filename]+qargs, env, os.path.dirname(self.filename))
 
138
 
 
139
 
 
140
class PHP3Script(FilteredScript):
 
141
    """I am a FilteredScript that uses the default PHP3 command on most systems.
 
142
    """
 
143
 
 
144
    filter = '/usr/bin/php3'
 
145
 
 
146
 
 
147
class PHPScript(FilteredScript):
 
148
    """I am a FilteredScript that uses the PHP command on most systems.
 
149
    Sometimes, php wants the path to itself as argv[0]. This is that time.
 
150
    """
 
151
 
 
152
    filter = '/usr/bin/php4'
 
153
 
 
154
 
 
155
class CGIProcessProtocol(protocol.ProcessProtocol, pb.Viewable):
 
156
    handling_headers = 1
 
157
    headers_written = 0
 
158
    headertext = ''
 
159
    errortext = ''
 
160
 
 
161
    # Remotely relay producer interface.
 
162
 
 
163
    def view_resumeProducing(self, issuer):
 
164
        self.resumeProducing()
 
165
 
 
166
    def view_pauseProducing(self, issuer):
 
167
        self.pauseProducing()
 
168
 
 
169
    def view_stopProducing(self, issuer):
 
170
        self.stopProducing()
 
171
 
 
172
    def resumeProducing(self):
 
173
        self.transport.resumeProducing()
 
174
 
 
175
    def pauseProducing(self):
 
176
        self.transport.pauseProducing()
 
177
 
 
178
    def stopProducing(self):
 
179
        self.transport.loseConnection()
 
180
 
 
181
    def __init__(self, request):
 
182
        self.request = request
 
183
 
 
184
    def connectionMade(self):
 
185
        self.request.registerProducer(self, 1)
 
186
        self.request.content.seek(0, 0)
 
187
        content = self.request.content.read()
 
188
        if content:
 
189
            self.transport.write(content)
 
190
        self.transport.closeStdin()
 
191
 
 
192
    def errReceived(self, error):
 
193
        self.errortext = self.errortext + error
 
194
 
 
195
    def outReceived(self, output):
 
196
        """
 
197
        Handle a chunk of input
 
198
        """
 
199
        # First, make sure that the headers from the script are sorted
 
200
        # out (we'll want to do some parsing on these later.)
 
201
        if self.handling_headers:
 
202
            text = self.headertext + output
 
203
            headerEnds = []
 
204
            for delimiter in '\n\n','\r\n\r\n','\r\r', '\n\r\n':
 
205
                headerend = string.find(text,delimiter)
 
206
                if headerend != -1:
 
207
                    headerEnds.append((headerend, delimiter))
 
208
            if headerEnds:
 
209
                headerEnds.sort()
 
210
                headerend, delimiter = headerEnds[0]
 
211
                self.headertext = text[:headerend]
 
212
                # This is a final version of the header text.
 
213
                linebreak = delimiter[:len(delimiter)/2]
 
214
                headers = string.split(self.headertext, linebreak)
 
215
                for header in headers:
 
216
                    br = string.find(header,': ')
 
217
                    if br == -1:
 
218
                        log.msg( 'ignoring malformed CGI header: %s' % header )
 
219
                    else:
 
220
                        headerName = string.lower(header[:br])
 
221
                        headerText = header[br+2:]
 
222
                        if headerName == 'location':
 
223
                            self.request.setResponseCode(http.FOUND)
 
224
                        if headerName == 'status':
 
225
                            try:
 
226
                                statusNum = int(headerText[:3]) #"XXX <description>" sometimes happens.
 
227
                            except:
 
228
                                log.msg( "malformed status header" )
 
229
                            else:
 
230
                                self.request.setResponseCode(statusNum)
 
231
                        else:
 
232
                            self.request.setHeader(headerName,headerText)
 
233
                output = text[headerend+len(delimiter):]
 
234
                self.handling_headers = 0
 
235
            if self.handling_headers:
 
236
                self.headertext = text
 
237
        if not self.handling_headers:
 
238
            self.request.write(output)
 
239
 
 
240
    def processEnded(self, reason):
 
241
        if reason.value.exitCode != 0:
 
242
            log.msg("CGI %s exited with exit code %s" %
 
243
                    (self.request.uri, reason.value.exitCode))
 
244
        if self.errortext:
 
245
            log.msg("Errors from CGI %s: %s" % (self.request.uri, self.errortext))
 
246
        if self.handling_headers:
 
247
            log.msg("Premature end of headers in %s: %s" % (self.request.uri, self.headertext))
 
248
            self.request.write(
 
249
                resource.ErrorPage(http.INTERNAL_SERVER_ERROR,
 
250
                                   "CGI Script Error",
 
251
                                   "Premature end of script headers.").render(self.request))
 
252
        self.request.unregisterProducer()
 
253
        self.request.finish()