~malept/ubuntu/lucid/python2.6/dev-dependency-fix

« back to all changes in this revision

Viewing changes to Lib/CGIHTTPServer.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-02-13 12:51:00 UTC
  • Revision ID: james.westby@ubuntu.com-20090213125100-uufgcb9yeqzujpqw
Tags: upstream-2.6.1
ImportĀ upstreamĀ versionĀ 2.6.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""CGI-savvy HTTP Server.
 
2
 
 
3
This module builds on SimpleHTTPServer by implementing GET and POST
 
4
requests to cgi-bin scripts.
 
5
 
 
6
If the os.fork() function is not present (e.g. on Windows),
 
7
os.popen2() is used as a fallback, with slightly altered semantics; if
 
8
that function is not present either (e.g. on Macintosh), only Python
 
9
scripts are supported, and they are executed by the current process.
 
10
 
 
11
In all cases, the implementation is intentionally naive -- all
 
12
requests are executed sychronously.
 
13
 
 
14
SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
 
15
-- it may execute arbitrary Python code or external programs.
 
16
 
 
17
Note that status code 200 is sent prior to execution of a CGI script, so
 
18
scripts cannot send other status codes such as 302 (redirect).
 
19
"""
 
20
 
 
21
 
 
22
__version__ = "0.4"
 
23
 
 
24
__all__ = ["CGIHTTPRequestHandler"]
 
25
 
 
26
import os
 
27
import sys
 
28
import urllib
 
29
import BaseHTTPServer
 
30
import SimpleHTTPServer
 
31
import select
 
32
 
 
33
 
 
34
class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
 
35
 
 
36
    """Complete HTTP server with GET, HEAD and POST commands.
 
37
 
 
38
    GET and HEAD also support running CGI scripts.
 
39
 
 
40
    The POST command is *only* implemented for CGI scripts.
 
41
 
 
42
    """
 
43
 
 
44
    # Determine platform specifics
 
45
    have_fork = hasattr(os, 'fork')
 
46
    have_popen2 = hasattr(os, 'popen2')
 
47
    have_popen3 = hasattr(os, 'popen3')
 
48
 
 
49
    # Make rfile unbuffered -- we need to read one line and then pass
 
50
    # the rest to a subprocess, so we can't use buffered input.
 
51
    rbufsize = 0
 
52
 
 
53
    def do_POST(self):
 
54
        """Serve a POST request.
 
55
 
 
56
        This is only implemented for CGI scripts.
 
57
 
 
58
        """
 
59
 
 
60
        if self.is_cgi():
 
61
            self.run_cgi()
 
62
        else:
 
63
            self.send_error(501, "Can only POST to CGI scripts")
 
64
 
 
65
    def send_head(self):
 
66
        """Version of send_head that support CGI scripts"""
 
67
        if self.is_cgi():
 
68
            return self.run_cgi()
 
69
        else:
 
70
            return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
 
71
 
 
72
    def is_cgi(self):
 
73
        """Test whether self.path corresponds to a CGI script,
 
74
        and return a boolean.
 
75
 
 
76
        This function sets self.cgi_info to a tuple (dir, rest)
 
77
        when it returns True, where dir is the directory part before
 
78
        the CGI script name.  Note that rest begins with a
 
79
        slash if it is not empty.
 
80
 
 
81
        The default implementation tests whether the path
 
82
        begins with one of the strings in the list
 
83
        self.cgi_directories (and the next character is a '/'
 
84
        or the end of the string).
 
85
        """
 
86
 
 
87
        path = self.path
 
88
 
 
89
        for x in self.cgi_directories:
 
90
            i = len(x)
 
91
            if path[:i] == x and (not path[i:] or path[i] == '/'):
 
92
                self.cgi_info = path[:i], path[i+1:]
 
93
                return True
 
94
        return False
 
95
 
 
96
    cgi_directories = ['/cgi-bin', '/htbin']
 
97
 
 
98
    def is_executable(self, path):
 
99
        """Test whether argument path is an executable file."""
 
100
        return executable(path)
 
101
 
 
102
    def is_python(self, path):
 
103
        """Test whether argument path is a Python script."""
 
104
        head, tail = os.path.splitext(path)
 
105
        return tail.lower() in (".py", ".pyw")
 
106
 
 
107
    def run_cgi(self):
 
108
        """Execute a CGI script."""
 
109
        path = self.path
 
110
        dir, rest = self.cgi_info
 
111
 
 
112
        i = path.find('/', len(dir) + 1)
 
113
        while i >= 0:
 
114
            nextdir = path[:i]
 
115
            nextrest = path[i+1:]
 
116
 
 
117
            scriptdir = self.translate_path(nextdir)
 
118
            if os.path.isdir(scriptdir):
 
119
                dir, rest = nextdir, nextrest
 
120
                i = path.find('/', len(dir) + 1)
 
121
            else:
 
122
                break
 
123
 
 
124
        # find an explicit query string, if present.
 
125
        i = rest.rfind('?')
 
126
        if i >= 0:
 
127
            rest, query = rest[:i], rest[i+1:]
 
128
        else:
 
129
            query = ''
 
130
 
 
131
        # dissect the part after the directory name into a script name &
 
132
        # a possible additional path, to be stored in PATH_INFO.
 
133
        i = rest.find('/')
 
134
        if i >= 0:
 
135
            script, rest = rest[:i], rest[i:]
 
136
        else:
 
137
            script, rest = rest, ''
 
138
 
 
139
        scriptname = dir + '/' + script
 
140
        scriptfile = self.translate_path(scriptname)
 
141
        if not os.path.exists(scriptfile):
 
142
            self.send_error(404, "No such CGI script (%r)" % scriptname)
 
143
            return
 
144
        if not os.path.isfile(scriptfile):
 
145
            self.send_error(403, "CGI script is not a plain file (%r)" %
 
146
                            scriptname)
 
147
            return
 
148
        ispy = self.is_python(scriptname)
 
149
        if not ispy:
 
150
            if not (self.have_fork or self.have_popen2 or self.have_popen3):
 
151
                self.send_error(403, "CGI script is not a Python script (%r)" %
 
152
                                scriptname)
 
153
                return
 
154
            if not self.is_executable(scriptfile):
 
155
                self.send_error(403, "CGI script is not executable (%r)" %
 
156
                                scriptname)
 
157
                return
 
158
 
 
159
        # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
 
160
        # XXX Much of the following could be prepared ahead of time!
 
161
        env = {}
 
162
        env['SERVER_SOFTWARE'] = self.version_string()
 
163
        env['SERVER_NAME'] = self.server.server_name
 
164
        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
 
165
        env['SERVER_PROTOCOL'] = self.protocol_version
 
166
        env['SERVER_PORT'] = str(self.server.server_port)
 
167
        env['REQUEST_METHOD'] = self.command
 
168
        uqrest = urllib.unquote(rest)
 
169
        env['PATH_INFO'] = uqrest
 
170
        env['PATH_TRANSLATED'] = self.translate_path(uqrest)
 
171
        env['SCRIPT_NAME'] = scriptname
 
172
        if query:
 
173
            env['QUERY_STRING'] = query
 
174
        host = self.address_string()
 
175
        if host != self.client_address[0]:
 
176
            env['REMOTE_HOST'] = host
 
177
        env['REMOTE_ADDR'] = self.client_address[0]
 
178
        authorization = self.headers.getheader("authorization")
 
179
        if authorization:
 
180
            authorization = authorization.split()
 
181
            if len(authorization) == 2:
 
182
                import base64, binascii
 
183
                env['AUTH_TYPE'] = authorization[0]
 
184
                if authorization[0].lower() == "basic":
 
185
                    try:
 
186
                        authorization = base64.decodestring(authorization[1])
 
187
                    except binascii.Error:
 
188
                        pass
 
189
                    else:
 
190
                        authorization = authorization.split(':')
 
191
                        if len(authorization) == 2:
 
192
                            env['REMOTE_USER'] = authorization[0]
 
193
        # XXX REMOTE_IDENT
 
194
        if self.headers.typeheader is None:
 
195
            env['CONTENT_TYPE'] = self.headers.type
 
196
        else:
 
197
            env['CONTENT_TYPE'] = self.headers.typeheader
 
198
        length = self.headers.getheader('content-length')
 
199
        if length:
 
200
            env['CONTENT_LENGTH'] = length
 
201
        referer = self.headers.getheader('referer')
 
202
        if referer:
 
203
            env['HTTP_REFERER'] = referer
 
204
        accept = []
 
205
        for line in self.headers.getallmatchingheaders('accept'):
 
206
            if line[:1] in "\t\n\r ":
 
207
                accept.append(line.strip())
 
208
            else:
 
209
                accept = accept + line[7:].split(',')
 
210
        env['HTTP_ACCEPT'] = ','.join(accept)
 
211
        ua = self.headers.getheader('user-agent')
 
212
        if ua:
 
213
            env['HTTP_USER_AGENT'] = ua
 
214
        co = filter(None, self.headers.getheaders('cookie'))
 
215
        if co:
 
216
            env['HTTP_COOKIE'] = ', '.join(co)
 
217
        # XXX Other HTTP_* headers
 
218
        # Since we're setting the env in the parent, provide empty
 
219
        # values to override previously set values
 
220
        for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
 
221
                  'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'):
 
222
            env.setdefault(k, "")
 
223
        os.environ.update(env)
 
224
 
 
225
        self.send_response(200, "Script output follows")
 
226
 
 
227
        decoded_query = query.replace('+', ' ')
 
228
 
 
229
        if self.have_fork:
 
230
            # Unix -- fork as we should
 
231
            args = [script]
 
232
            if '=' not in decoded_query:
 
233
                args.append(decoded_query)
 
234
            nobody = nobody_uid()
 
235
            self.wfile.flush() # Always flush before forking
 
236
            pid = os.fork()
 
237
            if pid != 0:
 
238
                # Parent
 
239
                pid, sts = os.waitpid(pid, 0)
 
240
                # throw away additional data [see bug #427345]
 
241
                while select.select([self.rfile], [], [], 0)[0]:
 
242
                    if not self.rfile.read(1):
 
243
                        break
 
244
                if sts:
 
245
                    self.log_error("CGI script exit status %#x", sts)
 
246
                return
 
247
            # Child
 
248
            try:
 
249
                try:
 
250
                    os.setuid(nobody)
 
251
                except os.error:
 
252
                    pass
 
253
                os.dup2(self.rfile.fileno(), 0)
 
254
                os.dup2(self.wfile.fileno(), 1)
 
255
                os.execve(scriptfile, args, os.environ)
 
256
            except:
 
257
                self.server.handle_error(self.request, self.client_address)
 
258
                os._exit(127)
 
259
 
 
260
        elif self.have_popen2 or self.have_popen3:
 
261
            # Windows -- use popen2 or popen3 to create a subprocess
 
262
            import shutil
 
263
            if self.have_popen3:
 
264
                popenx = os.popen3
 
265
            else:
 
266
                popenx = os.popen2
 
267
            cmdline = scriptfile
 
268
            if self.is_python(scriptfile):
 
269
                interp = sys.executable
 
270
                if interp.lower().endswith("w.exe"):
 
271
                    # On Windows, use python.exe, not pythonw.exe
 
272
                    interp = interp[:-5] + interp[-4:]
 
273
                cmdline = "%s -u %s" % (interp, cmdline)
 
274
            if '=' not in query and '"' not in query:
 
275
                cmdline = '%s "%s"' % (cmdline, query)
 
276
            self.log_message("command: %s", cmdline)
 
277
            try:
 
278
                nbytes = int(length)
 
279
            except (TypeError, ValueError):
 
280
                nbytes = 0
 
281
            files = popenx(cmdline, 'b')
 
282
            fi = files[0]
 
283
            fo = files[1]
 
284
            if self.have_popen3:
 
285
                fe = files[2]
 
286
            if self.command.lower() == "post" and nbytes > 0:
 
287
                data = self.rfile.read(nbytes)
 
288
                fi.write(data)
 
289
            # throw away additional data [see bug #427345]
 
290
            while select.select([self.rfile._sock], [], [], 0)[0]:
 
291
                if not self.rfile._sock.recv(1):
 
292
                    break
 
293
            fi.close()
 
294
            shutil.copyfileobj(fo, self.wfile)
 
295
            if self.have_popen3:
 
296
                errors = fe.read()
 
297
                fe.close()
 
298
                if errors:
 
299
                    self.log_error('%s', errors)
 
300
            sts = fo.close()
 
301
            if sts:
 
302
                self.log_error("CGI script exit status %#x", sts)
 
303
            else:
 
304
                self.log_message("CGI script exited OK")
 
305
 
 
306
        else:
 
307
            # Other O.S. -- execute script in this process
 
308
            save_argv = sys.argv
 
309
            save_stdin = sys.stdin
 
310
            save_stdout = sys.stdout
 
311
            save_stderr = sys.stderr
 
312
            try:
 
313
                save_cwd = os.getcwd()
 
314
                try:
 
315
                    sys.argv = [scriptfile]
 
316
                    if '=' not in decoded_query:
 
317
                        sys.argv.append(decoded_query)
 
318
                    sys.stdout = self.wfile
 
319
                    sys.stdin = self.rfile
 
320
                    execfile(scriptfile, {"__name__": "__main__"})
 
321
                finally:
 
322
                    sys.argv = save_argv
 
323
                    sys.stdin = save_stdin
 
324
                    sys.stdout = save_stdout
 
325
                    sys.stderr = save_stderr
 
326
                    os.chdir(save_cwd)
 
327
            except SystemExit, sts:
 
328
                self.log_error("CGI script exit status %s", str(sts))
 
329
            else:
 
330
                self.log_message("CGI script exited OK")
 
331
 
 
332
 
 
333
nobody = None
 
334
 
 
335
def nobody_uid():
 
336
    """Internal routine to get nobody's uid"""
 
337
    global nobody
 
338
    if nobody:
 
339
        return nobody
 
340
    try:
 
341
        import pwd
 
342
    except ImportError:
 
343
        return -1
 
344
    try:
 
345
        nobody = pwd.getpwnam('nobody')[2]
 
346
    except KeyError:
 
347
        nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
 
348
    return nobody
 
349
 
 
350
 
 
351
def executable(path):
 
352
    """Test for executable file."""
 
353
    try:
 
354
        st = os.stat(path)
 
355
    except os.error:
 
356
        return False
 
357
    return st.st_mode & 0111 != 0
 
358
 
 
359
 
 
360
def test(HandlerClass = CGIHTTPRequestHandler,
 
361
         ServerClass = BaseHTTPServer.HTTPServer):
 
362
    SimpleHTTPServer.test(HandlerClass, ServerClass)
 
363
 
 
364
 
 
365
if __name__ == '__main__':
 
366
    test()