blob: 29d701af2f360ca46b9af99191edd000d3b928ba [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."""
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000108 path = self.path
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000109 dir, rest = self.cgi_info
Thomas Wouters9fe394c2007-02-05 01:24:16 +0000110
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000111 i = path.find('/', len(dir) + 1)
112 while i >= 0:
113 nextdir = path[:i]
114 nextrest = path[i+1:]
115
116 scriptdir = self.translate_path(nextdir)
117 if os.path.isdir(scriptdir):
118 dir, rest = nextdir, nextrest
119 i = path.find('/', len(dir) + 1)
120 else:
121 break
122
123 # find an explicit query string, if present.
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000124 i = rest.rfind('?')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000125 if i >= 0:
126 rest, query = rest[:i], rest[i+1:]
127 else:
128 query = ''
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000129
130 # dissect the part after the directory name into a script name &
131 # a possible additional path, to be stored in PATH_INFO.
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000132 i = rest.find('/')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000133 if i >= 0:
134 script, rest = rest[:i], rest[i:]
135 else:
136 script, rest = rest, ''
Thomas Wouters902d6eb2007-01-09 23:18:33 +0000137
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000138 scriptname = dir + '/' + script
139 scriptfile = self.translate_path(scriptname)
140 if not os.path.exists(scriptfile):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000141 self.send_error(404, "No such CGI script (%r)" % scriptname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000142 return
143 if not os.path.isfile(scriptfile):
Tim Peters27f49612004-03-20 21:51:12 +0000144 self.send_error(403, "CGI script is not a plain file (%r)" %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000145 scriptname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000146 return
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000147 ispy = self.is_python(scriptname)
148 if not ispy:
Guido van Rossum8cb65402002-02-01 16:27:59 +0000149 if not (self.have_fork or self.have_popen2 or self.have_popen3):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000150 self.send_error(403, "CGI script is not a Python script (%r)" %
151 scriptname)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000152 return
153 if not self.is_executable(scriptfile):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000154 self.send_error(403, "CGI script is not executable (%r)" %
155 scriptname)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000156 return
157
158 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
159 # XXX Much of the following could be prepared ahead of time!
160 env = {}
161 env['SERVER_SOFTWARE'] = self.version_string()
162 env['SERVER_NAME'] = self.server.server_name
163 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
164 env['SERVER_PROTOCOL'] = self.protocol_version
165 env['SERVER_PORT'] = str(self.server.server_port)
166 env['REQUEST_METHOD'] = self.command
167 uqrest = urllib.unquote(rest)
168 env['PATH_INFO'] = uqrest
169 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
170 env['SCRIPT_NAME'] = scriptname
171 if query:
172 env['QUERY_STRING'] = query
173 host = self.address_string()
174 if host != self.client_address[0]:
175 env['REMOTE_HOST'] = host
176 env['REMOTE_ADDR'] = self.client_address[0]
Martin v. Löwisa28b3e62004-08-29 16:53:26 +0000177 authorization = self.headers.getheader("authorization")
178 if authorization:
179 authorization = authorization.split()
180 if len(authorization) == 2:
181 import base64, binascii
182 env['AUTH_TYPE'] = authorization[0]
183 if authorization[0].lower() == "basic":
184 try:
Georg Brandlb533e262008-05-25 18:19:30 +0000185 authorization = authorization[1].encode('ascii')
186 authorization = base64.decodestring(authorization).\
187 decode('ascii')
188 except (binascii.Error, UnicodeError):
Martin v. Löwisa28b3e62004-08-29 16:53:26 +0000189 pass
190 else:
191 authorization = authorization.split(':')
192 if len(authorization) == 2:
193 env['REMOTE_USER'] = authorization[0]
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000194 # XXX REMOTE_IDENT
195 if self.headers.typeheader is None:
196 env['CONTENT_TYPE'] = self.headers.type
197 else:
198 env['CONTENT_TYPE'] = self.headers.typeheader
199 length = self.headers.getheader('content-length')
200 if length:
201 env['CONTENT_LENGTH'] = length
Guido van Rossumd8faa362007-04-27 19:54:29 +0000202 referer = self.headers.getheader('referer')
203 if referer:
204 env['HTTP_REFERER'] = referer
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000205 accept = []
206 for line in self.headers.getallmatchingheaders('accept'):
Eric S. Raymond7e642e82001-02-09 12:10:26 +0000207 if line[:1] in "\t\n\r ":
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000208 accept.append(line.strip())
Guido van Rossum01fc65d1998-05-13 20:13:24 +0000209 else:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000210 accept = accept + line[7:].split(',')
211 env['HTTP_ACCEPT'] = ','.join(accept)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000212 ua = self.headers.getheader('user-agent')
213 if ua:
214 env['HTTP_USER_AGENT'] = ua
215 co = filter(None, self.headers.getheaders('cookie'))
216 if co:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000217 env['HTTP_COOKIE'] = ', '.join(co)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000218 # XXX Other HTTP_* headers
Guido van Rossum70ec0b42004-03-20 22:18:03 +0000219 # Since we're setting the env in the parent, provide empty
220 # values to override previously set values
221 for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
Guido van Rossumd8faa362007-04-27 19:54:29 +0000222 'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'):
Guido van Rossum70ec0b42004-03-20 22:18:03 +0000223 env.setdefault(k, "")
Guido van Rossume3ec2962002-08-20 20:07:10 +0000224 os.environ.update(env)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000225
226 self.send_response(200, "Script output follows")
227
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000228 decoded_query = query.replace('+', ' ')
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000229
230 if self.have_fork:
231 # Unix -- fork as we should
232 args = [script]
233 if '=' not in decoded_query:
234 args.append(decoded_query)
235 nobody = nobody_uid()
236 self.wfile.flush() # Always flush before forking
237 pid = os.fork()
238 if pid != 0:
239 # Parent
240 pid, sts = os.waitpid(pid, 0)
Steve Holden8a978f72003-01-08 18:53:18 +0000241 # throw away additional data [see bug #427345]
242 while select.select([self.rfile], [], [], 0)[0]:
Raymond Hettingere2f18372003-06-29 05:06:56 +0000243 if not self.rfile.read(1):
244 break
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000245 if sts:
246 self.log_error("CGI script exit status %#x", sts)
247 return
248 # Child
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000249 try:
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000250 try:
251 os.setuid(nobody)
252 except os.error:
253 pass
254 os.dup2(self.rfile.fileno(), 0)
255 os.dup2(self.wfile.fileno(), 1)
Raymond Hettinger92f200b2003-07-14 06:56:32 +0000256 os.execve(scriptfile, args, os.environ)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000257 except:
258 self.server.handle_error(self.request, self.client_address)
259 os._exit(127)
260
Guido van Rossum8cb65402002-02-01 16:27:59 +0000261 elif self.have_popen2 or self.have_popen3:
262 # Windows -- use popen2 or popen3 to create a subprocess
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000263 import shutil
Guido van Rossum8cb65402002-02-01 16:27:59 +0000264 if self.have_popen3:
265 popenx = os.popen3
266 else:
267 popenx = os.popen2
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000268 cmdline = scriptfile
269 if self.is_python(scriptfile):
270 interp = sys.executable
271 if interp.lower().endswith("w.exe"):
Guido van Rossum0afde132001-10-26 03:38:46 +0000272 # On Windows, use python.exe, not pythonw.exe
273 interp = interp[:-5] + interp[-4:]
Guido van Rossum16fd3382001-08-07 19:55:10 +0000274 cmdline = "%s -u %s" % (interp, cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000275 if '=' not in query and '"' not in query:
276 cmdline = '%s "%s"' % (cmdline, query)
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000277 self.log_message("command: %s", cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000278 try:
279 nbytes = int(length)
Guido van Rossumb3903152002-10-17 16:21:35 +0000280 except (TypeError, ValueError):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000281 nbytes = 0
Guido van Rossum8cb65402002-02-01 16:27:59 +0000282 files = popenx(cmdline, 'b')
283 fi = files[0]
284 fo = files[1]
285 if self.have_popen3:
286 fe = files[2]
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000287 if self.command.lower() == "post" and nbytes > 0:
288 data = self.rfile.read(nbytes)
289 fi.write(data)
Steve Holden8a978f72003-01-08 18:53:18 +0000290 # throw away additional data [see bug #427345]
291 while select.select([self.rfile._sock], [], [], 0)[0]:
Raymond Hettingere2f18372003-06-29 05:06:56 +0000292 if not self.rfile._sock.recv(1):
293 break
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000294 fi.close()
295 shutil.copyfileobj(fo, self.wfile)
Guido van Rossum8cb65402002-02-01 16:27:59 +0000296 if self.have_popen3:
297 errors = fe.read()
298 fe.close()
299 if errors:
300 self.log_error('%s', errors)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000301 sts = fo.close()
302 if sts:
303 self.log_error("CGI script exit status %#x", sts)
304 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000305 self.log_message("CGI script exited OK")
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000306
307 else:
308 # Other O.S. -- execute script in this process
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000309 save_argv = sys.argv
310 save_stdin = sys.stdin
311 save_stdout = sys.stdout
312 save_stderr = sys.stderr
313 try:
Tim Peters27f49612004-03-20 21:51:12 +0000314 save_cwd = os.getcwd()
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000315 try:
316 sys.argv = [scriptfile]
317 if '=' not in decoded_query:
318 sys.argv.append(decoded_query)
319 sys.stdout = self.wfile
320 sys.stdin = self.rfile
Neal Norwitz01688022007-08-12 00:43:29 +0000321 exec(open(scriptfile).read(), {"__name__": "__main__"})
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000322 finally:
323 sys.argv = save_argv
324 sys.stdin = save_stdin
325 sys.stdout = save_stdout
326 sys.stderr = save_stderr
Tim Peters27f49612004-03-20 21:51:12 +0000327 os.chdir(save_cwd)
Guido van Rossumb940e112007-01-10 16:19:56 +0000328 except SystemExit as sts:
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000329 self.log_error("CGI script exit status %s", str(sts))
330 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000331 self.log_message("CGI script exited OK")
Guido van Rossume7e578f1995-08-04 04:00:20 +0000332
333
334nobody = None
335
336def nobody_uid():
337 """Internal routine to get nobody's uid"""
338 global nobody
339 if nobody:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000340 return nobody
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000341 try:
342 import pwd
343 except ImportError:
344 return -1
Guido van Rossume7e578f1995-08-04 04:00:20 +0000345 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000346 nobody = pwd.getpwnam('nobody')[2]
Guido van Rossum630b8111999-04-28 12:21:47 +0000347 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000348 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
Guido van Rossume7e578f1995-08-04 04:00:20 +0000349 return nobody
350
351
352def executable(path):
353 """Test for executable file."""
354 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000355 st = os.stat(path)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000356 except os.error:
Guido van Rossum8ca162f2002-04-07 06:36:23 +0000357 return False
Guido van Rossumcd16bf62007-06-13 18:07:49 +0000358 return st.st_mode & 0o111 != 0
Guido van Rossume7e578f1995-08-04 04:00:20 +0000359
360
361def test(HandlerClass = CGIHTTPRequestHandler,
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000362 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossume7e578f1995-08-04 04:00:20 +0000363 SimpleHTTPServer.test(HandlerClass, ServerClass)
364
365
366if __name__ == '__main__':
367 test()