blob: a829621d7cdbcb6372fe7e568b98302eb678623b [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
Senthil Kumaran82759e42009-11-11 04:07:09 +0000260 else:
261 # Non Unix - use subprocess
262 import subprocess
263 cmdline = [scriptfile]
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000264 if self.is_python(scriptfile):
265 interp = sys.executable
266 if interp.lower().endswith("w.exe"):
Guido van Rossum0afde132001-10-26 03:38:46 +0000267 # On Windows, use python.exe, not pythonw.exe
268 interp = interp[:-5] + interp[-4:]
Senthil Kumaran82759e42009-11-11 04:07:09 +0000269 cmdline = [interp, '-u'] + cmdline
270 if '=' not in query:
271 cmdline.append(query)
272
273 self.log_message("command: %s", subprocess.list2cmdline(cmdline))
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000274 try:
275 nbytes = int(length)
Guido van Rossumb3903152002-10-17 16:21:35 +0000276 except (TypeError, ValueError):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000277 nbytes = 0
Senthil Kumaranf25702a2009-11-11 17:24:53 +0000278 p = subprocess.Popen(cmdline,
279 stdin = subprocess.PIPE,
280 stdout = subprocess.PIPE,
281 stderr = subprocess.PIPE
282 )
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000283 if self.command.lower() == "post" and nbytes > 0:
284 data = self.rfile.read(nbytes)
Senthil Kumaran82759e42009-11-11 04:07:09 +0000285 else:
286 data = None
Steve Holden8a978f72003-01-08 18:53:18 +0000287 # throw away additional data [see bug #427345]
288 while select.select([self.rfile._sock], [], [], 0)[0]:
Raymond Hettingere2f18372003-06-29 05:06:56 +0000289 if not self.rfile._sock.recv(1):
290 break
Senthil Kumaran82759e42009-11-11 04:07:09 +0000291 stdout, stderr = p.communicate(data)
292 self.wfile.write(stdout)
293 if stderr:
294 self.log_error('%s', stderr)
295 status = p.returncode
296 if status:
297 self.log_error("CGI script exit status %#x", status)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000298 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000299 self.log_message("CGI script exited OK")
Guido van Rossume7e578f1995-08-04 04:00:20 +0000300
301
302nobody = None
303
304def nobody_uid():
305 """Internal routine to get nobody's uid"""
306 global nobody
307 if nobody:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000308 return nobody
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000309 try:
310 import pwd
311 except ImportError:
312 return -1
Guido van Rossume7e578f1995-08-04 04:00:20 +0000313 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000314 nobody = pwd.getpwnam('nobody')[2]
Guido van Rossum630b8111999-04-28 12:21:47 +0000315 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000316 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
Guido van Rossume7e578f1995-08-04 04:00:20 +0000317 return nobody
318
319
320def executable(path):
321 """Test for executable file."""
322 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000323 st = os.stat(path)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000324 except os.error:
Guido van Rossum8ca162f2002-04-07 06:36:23 +0000325 return False
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000326 return st.st_mode & 0111 != 0
Guido van Rossume7e578f1995-08-04 04:00:20 +0000327
328
329def test(HandlerClass = CGIHTTPRequestHandler,
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000330 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossume7e578f1995-08-04 04:00:20 +0000331 SimpleHTTPServer.test(HandlerClass, ServerClass)
332
333
334if __name__ == '__main__':
335 test()