blob: f7b772268daad255408d65ad74e6f15a75f5f867 [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
Guido van Rossume7e578f1995-08-04 04:00:20 +000017"""
18
19
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000020__version__ = "0.4"
Guido van Rossume7e578f1995-08-04 04:00:20 +000021
Skip Montanaroe99d5ea2001-01-20 19:54:20 +000022__all__ = ["CGIHTTPRequestHandler"]
Guido van Rossume7e578f1995-08-04 04:00:20 +000023
24import os
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000025import sys
Guido van Rossume7e578f1995-08-04 04:00:20 +000026import urllib
27import BaseHTTPServer
28import SimpleHTTPServer
Steve Holden8a978f72003-01-08 18:53:18 +000029import select
Guido van Rossume7e578f1995-08-04 04:00:20 +000030
31
32class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
33
34 """Complete HTTP server with GET, HEAD and POST commands.
35
36 GET and HEAD also support running CGI scripts.
37
38 The POST command is *only* implemented for CGI scripts.
39
40 """
41
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000042 # Determine platform specifics
43 have_fork = hasattr(os, 'fork')
44 have_popen2 = hasattr(os, 'popen2')
Guido van Rossum8cb65402002-02-01 16:27:59 +000045 have_popen3 = hasattr(os, 'popen3')
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000046
Guido van Rossum6aefd912000-09-01 03:27:34 +000047 # Make rfile unbuffered -- we need to read one line and then pass
48 # the rest to a subprocess, so we can't use buffered input.
49 rbufsize = 0
50
Guido van Rossume7e578f1995-08-04 04:00:20 +000051 def do_POST(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000052 """Serve a POST request.
Guido van Rossume7e578f1995-08-04 04:00:20 +000053
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000054 This is only implemented for CGI scripts.
Guido van Rossume7e578f1995-08-04 04:00:20 +000055
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000056 """
Guido van Rossume7e578f1995-08-04 04:00:20 +000057
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000058 if self.is_cgi():
59 self.run_cgi()
60 else:
61 self.send_error(501, "Can only POST to CGI scripts")
Guido van Rossume7e578f1995-08-04 04:00:20 +000062
63 def send_head(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000064 """Version of send_head that support CGI scripts"""
65 if self.is_cgi():
66 return self.run_cgi()
67 else:
68 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
Guido van Rossume7e578f1995-08-04 04:00:20 +000069
70 def is_cgi(self):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000071 """Test whether self.path corresponds to a CGI script.
Guido van Rossume7e578f1995-08-04 04:00:20 +000072
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000073 Return a tuple (dir, rest) if self.path requires running a
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000074 CGI script, None if not. Note that rest begins with a
75 slash if it is not empty.
Guido van Rossume7e578f1995-08-04 04:00:20 +000076
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000077 The default implementation tests whether the path
78 begins with one of the strings in the list
79 self.cgi_directories (and the next character is a '/'
80 or the end of the string).
Guido van Rossume7e578f1995-08-04 04:00:20 +000081
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000082 """
Guido van Rossume7e578f1995-08-04 04:00:20 +000083
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000084 path = self.path
Guido van Rossume7e578f1995-08-04 04:00:20 +000085
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000086 for x in self.cgi_directories:
87 i = len(x)
88 if path[:i] == x and (not path[i:] or path[i] == '/'):
89 self.cgi_info = path[:i], path[i+1:]
Tim Petersbc0e9102002-04-04 22:55:58 +000090 return True
91 return False
Guido van Rossume7e578f1995-08-04 04:00:20 +000092
93 cgi_directories = ['/cgi-bin', '/htbin']
94
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000095 def is_executable(self, path):
96 """Test whether argument path is an executable file."""
97 return executable(path)
98
99 def is_python(self, path):
100 """Test whether argument path is a Python script."""
101 head, tail = os.path.splitext(path)
102 return tail.lower() in (".py", ".pyw")
103
Guido van Rossume7e578f1995-08-04 04:00:20 +0000104 def run_cgi(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000105 """Execute a CGI script."""
106 dir, rest = self.cgi_info
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000107 i = rest.rfind('?')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000108 if i >= 0:
109 rest, query = rest[:i], rest[i+1:]
110 else:
111 query = ''
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000112 i = rest.find('/')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000113 if i >= 0:
114 script, rest = rest[:i], rest[i:]
115 else:
116 script, rest = rest, ''
117 scriptname = dir + '/' + script
118 scriptfile = self.translate_path(scriptname)
119 if not os.path.exists(scriptfile):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000120 self.send_error(404, "No such CGI script (%r)" % scriptname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000121 return
122 if not os.path.isfile(scriptfile):
Tim Peters27f49612004-03-20 21:51:12 +0000123 self.send_error(403, "CGI script is not a plain file (%r)" %
Walter Dörwald70a6b492004-02-12 17:35:32 +0000124 scriptname)
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000125 return
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000126 ispy = self.is_python(scriptname)
127 if not ispy:
Guido van Rossum8cb65402002-02-01 16:27:59 +0000128 if not (self.have_fork or self.have_popen2 or self.have_popen3):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000129 self.send_error(403, "CGI script is not a Python script (%r)" %
130 scriptname)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000131 return
132 if not self.is_executable(scriptfile):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000133 self.send_error(403, "CGI script is not executable (%r)" %
134 scriptname)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000135 return
136
137 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
138 # XXX Much of the following could be prepared ahead of time!
139 env = {}
140 env['SERVER_SOFTWARE'] = self.version_string()
141 env['SERVER_NAME'] = self.server.server_name
142 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
143 env['SERVER_PROTOCOL'] = self.protocol_version
144 env['SERVER_PORT'] = str(self.server.server_port)
145 env['REQUEST_METHOD'] = self.command
146 uqrest = urllib.unquote(rest)
147 env['PATH_INFO'] = uqrest
148 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
149 env['SCRIPT_NAME'] = scriptname
150 if query:
151 env['QUERY_STRING'] = query
152 host = self.address_string()
153 if host != self.client_address[0]:
154 env['REMOTE_HOST'] = host
155 env['REMOTE_ADDR'] = self.client_address[0]
156 # XXX AUTH_TYPE
157 # XXX REMOTE_USER
158 # XXX REMOTE_IDENT
159 if self.headers.typeheader is None:
160 env['CONTENT_TYPE'] = self.headers.type
161 else:
162 env['CONTENT_TYPE'] = self.headers.typeheader
163 length = self.headers.getheader('content-length')
164 if length:
165 env['CONTENT_LENGTH'] = length
166 accept = []
167 for line in self.headers.getallmatchingheaders('accept'):
Eric S. Raymond7e642e82001-02-09 12:10:26 +0000168 if line[:1] in "\t\n\r ":
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000169 accept.append(line.strip())
Guido van Rossum01fc65d1998-05-13 20:13:24 +0000170 else:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000171 accept = accept + line[7:].split(',')
172 env['HTTP_ACCEPT'] = ','.join(accept)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000173 ua = self.headers.getheader('user-agent')
174 if ua:
175 env['HTTP_USER_AGENT'] = ua
176 co = filter(None, self.headers.getheaders('cookie'))
177 if co:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000178 env['HTTP_COOKIE'] = ', '.join(co)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000179 # XXX Other HTTP_* headers
Guido van Rossum70ec0b42004-03-20 22:18:03 +0000180 # Since we're setting the env in the parent, provide empty
181 # values to override previously set values
182 for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
183 'HTTP_USER_AGENT', 'HTTP_COOKIE'):
184 env.setdefault(k, "")
Guido van Rossume3ec2962002-08-20 20:07:10 +0000185 os.environ.update(env)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000186
187 self.send_response(200, "Script output follows")
188
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000189 decoded_query = query.replace('+', ' ')
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000190
191 if self.have_fork:
192 # Unix -- fork as we should
193 args = [script]
194 if '=' not in decoded_query:
195 args.append(decoded_query)
196 nobody = nobody_uid()
197 self.wfile.flush() # Always flush before forking
198 pid = os.fork()
199 if pid != 0:
200 # Parent
201 pid, sts = os.waitpid(pid, 0)
Steve Holden8a978f72003-01-08 18:53:18 +0000202 # throw away additional data [see bug #427345]
203 while select.select([self.rfile], [], [], 0)[0]:
Raymond Hettingere2f18372003-06-29 05:06:56 +0000204 if not self.rfile.read(1):
205 break
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000206 if sts:
207 self.log_error("CGI script exit status %#x", sts)
208 return
209 # Child
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000210 try:
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000211 try:
212 os.setuid(nobody)
213 except os.error:
214 pass
215 os.dup2(self.rfile.fileno(), 0)
216 os.dup2(self.wfile.fileno(), 1)
Raymond Hettinger92f200b2003-07-14 06:56:32 +0000217 os.execve(scriptfile, args, os.environ)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000218 except:
219 self.server.handle_error(self.request, self.client_address)
220 os._exit(127)
221
Guido van Rossum8cb65402002-02-01 16:27:59 +0000222 elif self.have_popen2 or self.have_popen3:
223 # Windows -- use popen2 or popen3 to create a subprocess
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000224 import shutil
Guido van Rossum8cb65402002-02-01 16:27:59 +0000225 if self.have_popen3:
226 popenx = os.popen3
227 else:
228 popenx = os.popen2
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000229 cmdline = scriptfile
230 if self.is_python(scriptfile):
231 interp = sys.executable
232 if interp.lower().endswith("w.exe"):
Guido van Rossum0afde132001-10-26 03:38:46 +0000233 # On Windows, use python.exe, not pythonw.exe
234 interp = interp[:-5] + interp[-4:]
Guido van Rossum16fd3382001-08-07 19:55:10 +0000235 cmdline = "%s -u %s" % (interp, cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000236 if '=' not in query and '"' not in query:
237 cmdline = '%s "%s"' % (cmdline, query)
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000238 self.log_message("command: %s", cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000239 try:
240 nbytes = int(length)
Guido van Rossumb3903152002-10-17 16:21:35 +0000241 except (TypeError, ValueError):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000242 nbytes = 0
Guido van Rossum8cb65402002-02-01 16:27:59 +0000243 files = popenx(cmdline, 'b')
244 fi = files[0]
245 fo = files[1]
246 if self.have_popen3:
247 fe = files[2]
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000248 if self.command.lower() == "post" and nbytes > 0:
249 data = self.rfile.read(nbytes)
250 fi.write(data)
Steve Holden8a978f72003-01-08 18:53:18 +0000251 # throw away additional data [see bug #427345]
252 while select.select([self.rfile._sock], [], [], 0)[0]:
Raymond Hettingere2f18372003-06-29 05:06:56 +0000253 if not self.rfile._sock.recv(1):
254 break
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000255 fi.close()
256 shutil.copyfileobj(fo, self.wfile)
Guido van Rossum8cb65402002-02-01 16:27:59 +0000257 if self.have_popen3:
258 errors = fe.read()
259 fe.close()
260 if errors:
261 self.log_error('%s', errors)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000262 sts = fo.close()
263 if sts:
264 self.log_error("CGI script exit status %#x", sts)
265 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000266 self.log_message("CGI script exited OK")
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000267
268 else:
269 # Other O.S. -- execute script in this process
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000270 save_argv = sys.argv
271 save_stdin = sys.stdin
272 save_stdout = sys.stdout
273 save_stderr = sys.stderr
274 try:
Tim Peters27f49612004-03-20 21:51:12 +0000275 save_cwd = os.getcwd()
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000276 try:
277 sys.argv = [scriptfile]
278 if '=' not in decoded_query:
279 sys.argv.append(decoded_query)
280 sys.stdout = self.wfile
281 sys.stdin = self.rfile
282 execfile(scriptfile, {"__name__": "__main__"})
283 finally:
284 sys.argv = save_argv
285 sys.stdin = save_stdin
286 sys.stdout = save_stdout
287 sys.stderr = save_stderr
Tim Peters27f49612004-03-20 21:51:12 +0000288 os.chdir(save_cwd)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000289 except SystemExit, sts:
290 self.log_error("CGI script exit status %s", str(sts))
291 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000292 self.log_message("CGI script exited OK")
Guido van Rossume7e578f1995-08-04 04:00:20 +0000293
294
295nobody = None
296
297def nobody_uid():
298 """Internal routine to get nobody's uid"""
299 global nobody
300 if nobody:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000301 return nobody
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000302 try:
303 import pwd
304 except ImportError:
305 return -1
Guido van Rossume7e578f1995-08-04 04:00:20 +0000306 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000307 nobody = pwd.getpwnam('nobody')[2]
Guido van Rossum630b8111999-04-28 12:21:47 +0000308 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000309 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
Guido van Rossume7e578f1995-08-04 04:00:20 +0000310 return nobody
311
312
313def executable(path):
314 """Test for executable file."""
315 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000316 st = os.stat(path)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000317 except os.error:
Guido van Rossum8ca162f2002-04-07 06:36:23 +0000318 return False
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000319 return st.st_mode & 0111 != 0
Guido van Rossume7e578f1995-08-04 04:00:20 +0000320
321
322def test(HandlerClass = CGIHTTPRequestHandler,
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000323 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossume7e578f1995-08-04 04:00:20 +0000324 SimpleHTTPServer.test(HandlerClass, ServerClass)
325
326
327if __name__ == '__main__':
328 test()