blob: 71f03687856071af72aced72043bee07450b90a2 [file] [log] [blame]
Guido van Rossume7e578f1995-08-04 04:00:20 +00001"""CGI-savvy HTTP Server.
2
3This module builds on SimpleHTTPServer by implementing GET and POST
4requests to cgi-bin scripts.
5
Guido van Rossume7d6b0a2000-09-19 04:01:01 +00006If the os.fork() function is not present (e.g. on Windows),
7os.popen2() is used as a fallback, with slightly altered semantics; if
8that function is not present either (e.g. on Macintosh), only Python
9scripts are supported, and they are executed by the current process.
10
11In all cases, the implementation is intentionally naive -- all
12requests are executed sychronously.
13
14SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
15-- it may execute arbitrary Python code or external programs.
Fred Drake40e84db1999-10-16 02:07:50 +000016
Jeremy Hylton6414cd82004-12-22 14:19:09 +000017Note that status code 200 is sent prior to execution of a CGI script, so
18scripts cannot send other status codes such as 302 (redirect).
Guido van Rossume7e578f1995-08-04 04:00:20 +000019"""
20
21
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000022__version__ = "0.4"
Guido van Rossume7e578f1995-08-04 04:00:20 +000023
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000024__all__ = ["CGIHTTPRequestHandler"]
Guido van Rossume7e578f1995-08-04 04:00:20 +000025
26import os
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000027import sys
Guido van Rossume7e578f1995-08-04 04:00:20 +000028import urllib
29import BaseHTTPServer
30import SimpleHTTPServer
Steve Holden8a978f72003-01-08 18:53:18 +000031import select
Guido van Rossume7e578f1995-08-04 04:00:20 +000032
33
34class 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
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000044 # Determine platform specifics
45 have_fork = hasattr(os, 'fork')
46 have_popen2 = hasattr(os, 'popen2')
Guido van Rossum8cb65402002-02-01 16:27:59 +000047 have_popen3 = hasattr(os, 'popen3')
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000048
Guido van Rossum6aefd912000-09-01 03:27:34 +000049 # 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
Guido van Rossume7e578f1995-08-04 04:00:20 +000053 def do_POST(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000054 """Serve a POST request.
Guido van Rossume7e578f1995-08-04 04:00:20 +000055
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000056 This is only implemented for CGI scripts.
Guido van Rossume7e578f1995-08-04 04:00:20 +000057
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000058 """
Guido van Rossume7e578f1995-08-04 04:00:20 +000059
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000060 if self.is_cgi():
61 self.run_cgi()
62 else:
63 self.send_error(501, "Can only POST to CGI scripts")
Guido van Rossume7e578f1995-08-04 04:00:20 +000064
65 def send_head(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000066 """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)
Guido van Rossume7e578f1995-08-04 04:00:20 +000071
72 def is_cgi(self):
Georg Brandl4ed9be72008-07-16 22:09:17 +000073 """Test whether self.path corresponds to a CGI script,
74 and return a boolean.
Guido van Rossume7e578f1995-08-04 04:00:20 +000075
Georg Brandl4ed9be72008-07-16 22:09:17 +000076 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
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000079 slash if it is not empty.
Guido van Rossume7e578f1995-08-04 04:00:20 +000080
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000081 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).
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000085 """
Guido van Rossume7e578f1995-08-04 04:00:20 +000086
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000087 path = self.path
Guido van Rossume7e578f1995-08-04 04:00:20 +000088
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000089 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:]
Tim Petersbc0e9102002-04-04 22:55:58 +000093 return True
94 return False
Guido van Rossume7e578f1995-08-04 04:00:20 +000095
96 cgi_directories = ['/cgi-bin', '/htbin']
97
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000098 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
Guido van Rossume7e578f1995-08-04 04:00:20 +0000107 def run_cgi(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000108 """Execute a CGI script."""
Andrew M. Kuchlingb29069d2006-12-22 13:25:02 +0000109 path = self.path
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000110 dir, rest = self.cgi_info
Tim Petersf733abb2007-01-30 03:03:46 +0000111
Andrew M. Kuchlingb29069d2006-12-22 13:25:02 +0000112 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.
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000125 i = rest.rfind('?')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000126 if i >= 0:
127 rest, query = rest[:i], rest[i+1:]
128 else:
129 query = ''
Andrew M. Kuchlingb29069d2006-12-22 13:25:02 +0000130
131 # dissect the part after the directory name into a script name &
132 # a possible additional path, to be stored in PATH_INFO.
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000133 i = rest.find('/')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000134 if i >= 0:
135 script, rest = rest[:i], rest[i:]
136 else:
137 script, rest = rest, ''
Andrew M. Kuchlingb29069d2006-12-22 13:25:02 +0000138
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000139 scriptname = dir + '/' + script
140 scriptfile = self.translate_path(scriptname)
141 if not os.path.exists(scriptfile):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000142 self.send_error(404, "No such CGI script (%r)" % scriptname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000143 return
144 if not os.path.isfile(scriptfile):
Tim Peters27f49612004-03-20 21:51:12 +0000145 self.send_error(403, "CGI script is not a plain file (%r)" %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000146 scriptname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000147 return
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000148 ispy = self.is_python(scriptname)
149 if not ispy:
Guido van Rossum8cb65402002-02-01 16:27:59 +0000150 if not (self.have_fork or self.have_popen2 or self.have_popen3):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000151 self.send_error(403, "CGI script is not a Python script (%r)" %
152 scriptname)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000153 return
154 if not self.is_executable(scriptfile):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000155 self.send_error(403, "CGI script is not executable (%r)" %
156 scriptname)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000157 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]
Martin v. Löwisa28b3e62004-08-29 16:53:26 +0000178 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]
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000193 # 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
Collin Winter83b2bf62007-03-09 03:15:56 +0000201 referer = self.headers.getheader('referer')
202 if referer:
203 env['HTTP_REFERER'] = referer
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000204 accept = []
205 for line in self.headers.getallmatchingheaders('accept'):
Eric S. Raymond7e642e82001-02-09 12:10:26 +0000206 if line[:1] in "\t\n\r ":
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000207 accept.append(line.strip())
Guido van Rossum01fc65d1998-05-13 20:13:24 +0000208 else:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000209 accept = accept + line[7:].split(',')
210 env['HTTP_ACCEPT'] = ','.join(accept)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000211 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:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000216 env['HTTP_COOKIE'] = ', '.join(co)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000217 # XXX Other HTTP_* headers
Guido van Rossum70ec0b42004-03-20 22:18:03 +0000218 # 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',
Collin Winter83b2bf62007-03-09 03:15:56 +0000221 'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'):
Guido van Rossum70ec0b42004-03-20 22:18:03 +0000222 env.setdefault(k, "")
Guido van Rossume3ec2962002-08-20 20:07:10 +0000223 os.environ.update(env)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000224
225 self.send_response(200, "Script output follows")
226
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000227 decoded_query = query.replace('+', ' ')
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000228
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)
Steve Holden8a978f72003-01-08 18:53:18 +0000240 # throw away additional data [see bug #427345]
241 while select.select([self.rfile], [], [], 0)[0]:
Raymond Hettingere2f18372003-06-29 05:06:56 +0000242 if not self.rfile.read(1):
243 break
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000244 if sts:
245 self.log_error("CGI script exit status %#x", sts)
246 return
247 # Child
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000248 try:
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000249 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)
Raymond Hettinger92f200b2003-07-14 06:56:32 +0000255 os.execve(scriptfile, args, os.environ)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000256 except:
257 self.server.handle_error(self.request, self.client_address)
258 os._exit(127)
259
Guido van Rossum8cb65402002-02-01 16:27:59 +0000260 elif self.have_popen2 or self.have_popen3:
261 # Windows -- use popen2 or popen3 to create a subprocess
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000262 import shutil
Guido van Rossum8cb65402002-02-01 16:27:59 +0000263 if self.have_popen3:
264 popenx = os.popen3
265 else:
266 popenx = os.popen2
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000267 cmdline = scriptfile
268 if self.is_python(scriptfile):
269 interp = sys.executable
270 if interp.lower().endswith("w.exe"):
Guido van Rossum0afde132001-10-26 03:38:46 +0000271 # On Windows, use python.exe, not pythonw.exe
272 interp = interp[:-5] + interp[-4:]
Guido van Rossum16fd3382001-08-07 19:55:10 +0000273 cmdline = "%s -u %s" % (interp, cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000274 if '=' not in query and '"' not in query:
275 cmdline = '%s "%s"' % (cmdline, query)
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000276 self.log_message("command: %s", cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000277 try:
278 nbytes = int(length)
Guido van Rossumb3903152002-10-17 16:21:35 +0000279 except (TypeError, ValueError):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000280 nbytes = 0
Guido van Rossum8cb65402002-02-01 16:27:59 +0000281 files = popenx(cmdline, 'b')
282 fi = files[0]
283 fo = files[1]
284 if self.have_popen3:
285 fe = files[2]
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000286 if self.command.lower() == "post" and nbytes > 0:
287 data = self.rfile.read(nbytes)
288 fi.write(data)
Steve Holden8a978f72003-01-08 18:53:18 +0000289 # throw away additional data [see bug #427345]
290 while select.select([self.rfile._sock], [], [], 0)[0]:
Raymond Hettingere2f18372003-06-29 05:06:56 +0000291 if not self.rfile._sock.recv(1):
292 break
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000293 fi.close()
294 shutil.copyfileobj(fo, self.wfile)
Guido van Rossum8cb65402002-02-01 16:27:59 +0000295 if self.have_popen3:
296 errors = fe.read()
297 fe.close()
298 if errors:
299 self.log_error('%s', errors)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000300 sts = fo.close()
301 if sts:
302 self.log_error("CGI script exit status %#x", sts)
303 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000304 self.log_message("CGI script exited OK")
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000305
306 else:
307 # Other O.S. -- execute script in this process
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000308 save_argv = sys.argv
309 save_stdin = sys.stdin
310 save_stdout = sys.stdout
311 save_stderr = sys.stderr
312 try:
Tim Peters27f49612004-03-20 21:51:12 +0000313 save_cwd = os.getcwd()
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000314 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
Tim Peters27f49612004-03-20 21:51:12 +0000326 os.chdir(save_cwd)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000327 except SystemExit, sts:
328 self.log_error("CGI script exit status %s", str(sts))
329 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000330 self.log_message("CGI script exited OK")
Guido van Rossume7e578f1995-08-04 04:00:20 +0000331
332
333nobody = None
334
335def nobody_uid():
336 """Internal routine to get nobody's uid"""
337 global nobody
338 if nobody:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000339 return nobody
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000340 try:
341 import pwd
342 except ImportError:
343 return -1
Guido van Rossume7e578f1995-08-04 04:00:20 +0000344 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000345 nobody = pwd.getpwnam('nobody')[2]
Guido van Rossum630b8111999-04-28 12:21:47 +0000346 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000347 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
Guido van Rossume7e578f1995-08-04 04:00:20 +0000348 return nobody
349
350
351def executable(path):
352 """Test for executable file."""
353 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000354 st = os.stat(path)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000355 except os.error:
Guido van Rossum8ca162f2002-04-07 06:36:23 +0000356 return False
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000357 return st.st_mode & 0111 != 0
Guido van Rossume7e578f1995-08-04 04:00:20 +0000358
359
360def test(HandlerClass = CGIHTTPRequestHandler,
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000361 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossume7e578f1995-08-04 04:00:20 +0000362 SimpleHTTPServer.test(HandlerClass, ServerClass)
363
364
365if __name__ == '__main__':
366 test()