blob: 7a5c8190d82c689d8dd52293ad345be07de402ad [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):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000073 """Test whether self.path corresponds to a CGI script.
Guido van Rossume7e578f1995-08-04 04:00:20 +000074
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000075 Return a tuple (dir, rest) if self.path requires running a
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000076 CGI script, None if not. Note that rest begins with a
77 slash if it is not empty.
Guido van Rossume7e578f1995-08-04 04:00:20 +000078
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000079 The default implementation tests whether the path
80 begins with one of the strings in the list
81 self.cgi_directories (and the next character is a '/'
82 or the end of the string).
Guido van Rossume7e578f1995-08-04 04:00:20 +000083
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000084 """
Guido van Rossume7e578f1995-08-04 04:00:20 +000085
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000086 path = self.path
Guido van Rossume7e578f1995-08-04 04:00:20 +000087
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000088 for x in self.cgi_directories:
89 i = len(x)
90 if path[:i] == x and (not path[i:] or path[i] == '/'):
91 self.cgi_info = path[:i], path[i+1:]
Tim Petersbc0e9102002-04-04 22:55:58 +000092 return True
93 return False
Guido van Rossume7e578f1995-08-04 04:00:20 +000094
95 cgi_directories = ['/cgi-bin', '/htbin']
96
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000097 def is_executable(self, path):
98 """Test whether argument path is an executable file."""
99 return executable(path)
100
101 def is_python(self, path):
102 """Test whether argument path is a Python script."""
103 head, tail = os.path.splitext(path)
104 return tail.lower() in (".py", ".pyw")
105
Guido van Rossume7e578f1995-08-04 04:00:20 +0000106 def run_cgi(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000107 """Execute a CGI script."""
108 dir, rest = self.cgi_info
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000109 i = rest.rfind('?')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000110 if i >= 0:
111 rest, query = rest[:i], rest[i+1:]
112 else:
113 query = ''
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000114 i = rest.find('/')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000115 if i >= 0:
116 script, rest = rest[:i], rest[i:]
117 else:
118 script, rest = rest, ''
119 scriptname = dir + '/' + script
120 scriptfile = self.translate_path(scriptname)
121 if not os.path.exists(scriptfile):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000122 self.send_error(404, "No such CGI script (%r)" % scriptname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000123 return
124 if not os.path.isfile(scriptfile):
Tim Peters27f49612004-03-20 21:51:12 +0000125 self.send_error(403, "CGI script is not a plain file (%r)" %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000126 scriptname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000127 return
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000128 ispy = self.is_python(scriptname)
129 if not ispy:
Guido van Rossum8cb65402002-02-01 16:27:59 +0000130 if not (self.have_fork or self.have_popen2 or self.have_popen3):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000131 self.send_error(403, "CGI script is not a Python script (%r)" %
132 scriptname)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000133 return
134 if not self.is_executable(scriptfile):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000135 self.send_error(403, "CGI script is not executable (%r)" %
136 scriptname)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000137 return
138
139 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
140 # XXX Much of the following could be prepared ahead of time!
141 env = {}
142 env['SERVER_SOFTWARE'] = self.version_string()
143 env['SERVER_NAME'] = self.server.server_name
144 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
145 env['SERVER_PROTOCOL'] = self.protocol_version
146 env['SERVER_PORT'] = str(self.server.server_port)
147 env['REQUEST_METHOD'] = self.command
148 uqrest = urllib.unquote(rest)
149 env['PATH_INFO'] = uqrest
150 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
151 env['SCRIPT_NAME'] = scriptname
152 if query:
153 env['QUERY_STRING'] = query
154 host = self.address_string()
155 if host != self.client_address[0]:
156 env['REMOTE_HOST'] = host
157 env['REMOTE_ADDR'] = self.client_address[0]
Martin v. Löwisa28b3e62004-08-29 16:53:26 +0000158 authorization = self.headers.getheader("authorization")
159 if authorization:
160 authorization = authorization.split()
161 if len(authorization) == 2:
162 import base64, binascii
163 env['AUTH_TYPE'] = authorization[0]
164 if authorization[0].lower() == "basic":
165 try:
166 authorization = base64.decodestring(authorization[1])
167 except binascii.Error:
168 pass
169 else:
170 authorization = authorization.split(':')
171 if len(authorization) == 2:
172 env['REMOTE_USER'] = authorization[0]
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000173 # XXX REMOTE_IDENT
174 if self.headers.typeheader is None:
175 env['CONTENT_TYPE'] = self.headers.type
176 else:
177 env['CONTENT_TYPE'] = self.headers.typeheader
178 length = self.headers.getheader('content-length')
179 if length:
180 env['CONTENT_LENGTH'] = length
181 accept = []
182 for line in self.headers.getallmatchingheaders('accept'):
Eric S. Raymond7e642e82001-02-09 12:10:26 +0000183 if line[:1] in "\t\n\r ":
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000184 accept.append(line.strip())
Guido van Rossum01fc65d1998-05-13 20:13:24 +0000185 else:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000186 accept = accept + line[7:].split(',')
187 env['HTTP_ACCEPT'] = ','.join(accept)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000188 ua = self.headers.getheader('user-agent')
189 if ua:
190 env['HTTP_USER_AGENT'] = ua
191 co = filter(None, self.headers.getheaders('cookie'))
192 if co:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000193 env['HTTP_COOKIE'] = ', '.join(co)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000194 # XXX Other HTTP_* headers
Guido van Rossum70ec0b42004-03-20 22:18:03 +0000195 # Since we're setting the env in the parent, provide empty
196 # values to override previously set values
197 for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
198 'HTTP_USER_AGENT', 'HTTP_COOKIE'):
199 env.setdefault(k, "")
Guido van Rossume3ec2962002-08-20 20:07:10 +0000200 os.environ.update(env)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000201
202 self.send_response(200, "Script output follows")
203
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000204 decoded_query = query.replace('+', ' ')
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000205
206 if self.have_fork:
207 # Unix -- fork as we should
208 args = [script]
209 if '=' not in decoded_query:
210 args.append(decoded_query)
211 nobody = nobody_uid()
212 self.wfile.flush() # Always flush before forking
213 pid = os.fork()
214 if pid != 0:
215 # Parent
216 pid, sts = os.waitpid(pid, 0)
Steve Holden8a978f72003-01-08 18:53:18 +0000217 # throw away additional data [see bug #427345]
218 while select.select([self.rfile], [], [], 0)[0]:
Raymond Hettingere2f18372003-06-29 05:06:56 +0000219 if not self.rfile.read(1):
220 break
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000221 if sts:
222 self.log_error("CGI script exit status %#x", sts)
223 return
224 # Child
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000225 try:
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000226 try:
227 os.setuid(nobody)
228 except os.error:
229 pass
230 os.dup2(self.rfile.fileno(), 0)
231 os.dup2(self.wfile.fileno(), 1)
Raymond Hettinger92f200b2003-07-14 06:56:32 +0000232 os.execve(scriptfile, args, os.environ)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000233 except:
234 self.server.handle_error(self.request, self.client_address)
235 os._exit(127)
236
Guido van Rossum8cb65402002-02-01 16:27:59 +0000237 elif self.have_popen2 or self.have_popen3:
238 # Windows -- use popen2 or popen3 to create a subprocess
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000239 import shutil
Guido van Rossum8cb65402002-02-01 16:27:59 +0000240 if self.have_popen3:
241 popenx = os.popen3
242 else:
243 popenx = os.popen2
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000244 cmdline = scriptfile
245 if self.is_python(scriptfile):
246 interp = sys.executable
247 if interp.lower().endswith("w.exe"):
Guido van Rossum0afde132001-10-26 03:38:46 +0000248 # On Windows, use python.exe, not pythonw.exe
249 interp = interp[:-5] + interp[-4:]
Guido van Rossum16fd3382001-08-07 19:55:10 +0000250 cmdline = "%s -u %s" % (interp, cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000251 if '=' not in query and '"' not in query:
252 cmdline = '%s "%s"' % (cmdline, query)
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000253 self.log_message("command: %s", cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000254 try:
255 nbytes = int(length)
Guido van Rossumb3903152002-10-17 16:21:35 +0000256 except (TypeError, ValueError):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000257 nbytes = 0
Guido van Rossum8cb65402002-02-01 16:27:59 +0000258 files = popenx(cmdline, 'b')
259 fi = files[0]
260 fo = files[1]
261 if self.have_popen3:
262 fe = files[2]
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000263 if self.command.lower() == "post" and nbytes > 0:
264 data = self.rfile.read(nbytes)
265 fi.write(data)
Steve Holden8a978f72003-01-08 18:53:18 +0000266 # throw away additional data [see bug #427345]
267 while select.select([self.rfile._sock], [], [], 0)[0]:
Raymond Hettingere2f18372003-06-29 05:06:56 +0000268 if not self.rfile._sock.recv(1):
269 break
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000270 fi.close()
271 shutil.copyfileobj(fo, self.wfile)
Guido van Rossum8cb65402002-02-01 16:27:59 +0000272 if self.have_popen3:
273 errors = fe.read()
274 fe.close()
275 if errors:
276 self.log_error('%s', errors)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000277 sts = fo.close()
278 if sts:
279 self.log_error("CGI script exit status %#x", sts)
280 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000281 self.log_message("CGI script exited OK")
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000282
283 else:
284 # Other O.S. -- execute script in this process
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000285 save_argv = sys.argv
286 save_stdin = sys.stdin
287 save_stdout = sys.stdout
288 save_stderr = sys.stderr
289 try:
Tim Peters27f49612004-03-20 21:51:12 +0000290 save_cwd = os.getcwd()
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000291 try:
292 sys.argv = [scriptfile]
293 if '=' not in decoded_query:
294 sys.argv.append(decoded_query)
295 sys.stdout = self.wfile
296 sys.stdin = self.rfile
297 execfile(scriptfile, {"__name__": "__main__"})
298 finally:
299 sys.argv = save_argv
300 sys.stdin = save_stdin
301 sys.stdout = save_stdout
302 sys.stderr = save_stderr
Tim Peters27f49612004-03-20 21:51:12 +0000303 os.chdir(save_cwd)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000304 except SystemExit, sts:
305 self.log_error("CGI script exit status %s", str(sts))
306 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000307 self.log_message("CGI script exited OK")
Guido van Rossume7e578f1995-08-04 04:00:20 +0000308
309
310nobody = None
311
312def nobody_uid():
313 """Internal routine to get nobody's uid"""
314 global nobody
315 if nobody:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000316 return nobody
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000317 try:
318 import pwd
319 except ImportError:
320 return -1
Guido van Rossume7e578f1995-08-04 04:00:20 +0000321 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000322 nobody = pwd.getpwnam('nobody')[2]
Guido van Rossum630b8111999-04-28 12:21:47 +0000323 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000324 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
Guido van Rossume7e578f1995-08-04 04:00:20 +0000325 return nobody
326
327
328def executable(path):
329 """Test for executable file."""
330 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000331 st = os.stat(path)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000332 except os.error:
Guido van Rossum8ca162f2002-04-07 06:36:23 +0000333 return False
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000334 return st.st_mode & 0111 != 0
Guido van Rossume7e578f1995-08-04 04:00:20 +0000335
336
337def test(HandlerClass = CGIHTTPRequestHandler,
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000338 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossume7e578f1995-08-04 04:00:20 +0000339 SimpleHTTPServer.test(HandlerClass, ServerClass)
340
341
342if __name__ == '__main__':
343 test()