blob: 6c3e09ea9bef3801f6da2bd409563c2215635b89 [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
29
30
31class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
32
33 """Complete HTTP server with GET, HEAD and POST commands.
34
35 GET and HEAD also support running CGI scripts.
36
37 The POST command is *only* implemented for CGI scripts.
38
39 """
40
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000041 # Determine platform specifics
42 have_fork = hasattr(os, 'fork')
43 have_popen2 = hasattr(os, 'popen2')
Guido van Rossum8cb65402002-02-01 16:27:59 +000044 have_popen3 = hasattr(os, 'popen3')
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000045
Guido van Rossum6aefd912000-09-01 03:27:34 +000046 # Make rfile unbuffered -- we need to read one line and then pass
47 # the rest to a subprocess, so we can't use buffered input.
48 rbufsize = 0
49
Guido van Rossume7e578f1995-08-04 04:00:20 +000050 def do_POST(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000051 """Serve a POST request.
Guido van Rossume7e578f1995-08-04 04:00:20 +000052
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000053 This is only implemented for CGI scripts.
Guido van Rossume7e578f1995-08-04 04:00:20 +000054
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000055 """
Guido van Rossume7e578f1995-08-04 04:00:20 +000056
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000057 if self.is_cgi():
58 self.run_cgi()
59 else:
60 self.send_error(501, "Can only POST to CGI scripts")
Guido van Rossume7e578f1995-08-04 04:00:20 +000061
62 def send_head(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000063 """Version of send_head that support CGI scripts"""
64 if self.is_cgi():
65 return self.run_cgi()
66 else:
67 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
Guido van Rossume7e578f1995-08-04 04:00:20 +000068
69 def is_cgi(self):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000070 """Test whether self.path corresponds to a CGI script.
Guido van Rossume7e578f1995-08-04 04:00:20 +000071
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000072 Return a tuple (dir, rest) if self.path requires running a
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000073 CGI script, None if not. Note that rest begins with a
74 slash if it is not empty.
Guido van Rossume7e578f1995-08-04 04:00:20 +000075
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000076 The default implementation tests whether the path
77 begins with one of the strings in the list
78 self.cgi_directories (and the next character is a '/'
79 or the end of the string).
Guido van Rossume7e578f1995-08-04 04:00:20 +000080
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000081 """
Guido van Rossume7e578f1995-08-04 04:00:20 +000082
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000083 path = self.path
Guido van Rossume7e578f1995-08-04 04:00:20 +000084
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000085 for x in self.cgi_directories:
86 i = len(x)
87 if path[:i] == x and (not path[i:] or path[i] == '/'):
88 self.cgi_info = path[:i], path[i+1:]
Tim Petersbc0e9102002-04-04 22:55:58 +000089 return True
90 return False
Guido van Rossume7e578f1995-08-04 04:00:20 +000091
92 cgi_directories = ['/cgi-bin', '/htbin']
93
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000094 def is_executable(self, path):
95 """Test whether argument path is an executable file."""
96 return executable(path)
97
98 def is_python(self, path):
99 """Test whether argument path is a Python script."""
100 head, tail = os.path.splitext(path)
101 return tail.lower() in (".py", ".pyw")
102
Guido van Rossume7e578f1995-08-04 04:00:20 +0000103 def run_cgi(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000104 """Execute a CGI script."""
105 dir, rest = self.cgi_info
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000106 i = rest.rfind('?')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000107 if i >= 0:
108 rest, query = rest[:i], rest[i+1:]
109 else:
110 query = ''
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000111 i = rest.find('/')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000112 if i >= 0:
113 script, rest = rest[:i], rest[i:]
114 else:
115 script, rest = rest, ''
116 scriptname = dir + '/' + script
117 scriptfile = self.translate_path(scriptname)
118 if not os.path.exists(scriptfile):
119 self.send_error(404, "No such CGI script (%s)" % `scriptname`)
120 return
121 if not os.path.isfile(scriptfile):
122 self.send_error(403, "CGI script is not a plain file (%s)" %
123 `scriptname`)
124 return
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000125 ispy = self.is_python(scriptname)
126 if not ispy:
Guido van Rossum8cb65402002-02-01 16:27:59 +0000127 if not (self.have_fork or self.have_popen2 or self.have_popen3):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000128 self.send_error(403, "CGI script is not a Python script (%s)" %
129 `scriptname`)
130 return
131 if not self.is_executable(scriptfile):
132 self.send_error(403, "CGI script is not executable (%s)" %
133 `scriptname`)
134 return
135
136 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
137 # XXX Much of the following could be prepared ahead of time!
138 env = {}
139 env['SERVER_SOFTWARE'] = self.version_string()
140 env['SERVER_NAME'] = self.server.server_name
141 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
142 env['SERVER_PROTOCOL'] = self.protocol_version
143 env['SERVER_PORT'] = str(self.server.server_port)
144 env['REQUEST_METHOD'] = self.command
145 uqrest = urllib.unquote(rest)
146 env['PATH_INFO'] = uqrest
147 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
148 env['SCRIPT_NAME'] = scriptname
149 if query:
150 env['QUERY_STRING'] = query
151 host = self.address_string()
152 if host != self.client_address[0]:
153 env['REMOTE_HOST'] = host
154 env['REMOTE_ADDR'] = self.client_address[0]
155 # XXX AUTH_TYPE
156 # XXX REMOTE_USER
157 # XXX REMOTE_IDENT
158 if self.headers.typeheader is None:
159 env['CONTENT_TYPE'] = self.headers.type
160 else:
161 env['CONTENT_TYPE'] = self.headers.typeheader
162 length = self.headers.getheader('content-length')
163 if length:
164 env['CONTENT_LENGTH'] = length
165 accept = []
166 for line in self.headers.getallmatchingheaders('accept'):
Eric S. Raymond7e642e82001-02-09 12:10:26 +0000167 if line[:1] in "\t\n\r ":
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000168 accept.append(line.strip())
Guido van Rossum01fc65d1998-05-13 20:13:24 +0000169 else:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000170 accept = accept + line[7:].split(',')
171 env['HTTP_ACCEPT'] = ','.join(accept)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000172 ua = self.headers.getheader('user-agent')
173 if ua:
174 env['HTTP_USER_AGENT'] = ua
175 co = filter(None, self.headers.getheaders('cookie'))
176 if co:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000177 env['HTTP_COOKIE'] = ', '.join(co)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000178 # XXX Other HTTP_* headers
179 if not self.have_fork:
180 # 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)
202 if sts:
203 self.log_error("CGI script exit status %#x", sts)
204 return
205 # Child
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000206 try:
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000207 try:
208 os.setuid(nobody)
209 except os.error:
210 pass
211 os.dup2(self.rfile.fileno(), 0)
212 os.dup2(self.wfile.fileno(), 1)
213 os.execve(scriptfile, args, env)
214 except:
215 self.server.handle_error(self.request, self.client_address)
216 os._exit(127)
217
Guido van Rossum8cb65402002-02-01 16:27:59 +0000218 elif self.have_popen2 or self.have_popen3:
219 # Windows -- use popen2 or popen3 to create a subprocess
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000220 import shutil
Guido van Rossum8cb65402002-02-01 16:27:59 +0000221 if self.have_popen3:
222 popenx = os.popen3
223 else:
224 popenx = os.popen2
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000225 cmdline = scriptfile
226 if self.is_python(scriptfile):
227 interp = sys.executable
228 if interp.lower().endswith("w.exe"):
Guido van Rossum0afde132001-10-26 03:38:46 +0000229 # On Windows, use python.exe, not pythonw.exe
230 interp = interp[:-5] + interp[-4:]
Guido van Rossum16fd3382001-08-07 19:55:10 +0000231 cmdline = "%s -u %s" % (interp, cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000232 if '=' not in query and '"' not in query:
233 cmdline = '%s "%s"' % (cmdline, query)
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000234 self.log_message("command: %s", cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000235 try:
236 nbytes = int(length)
Skip Montanaro3c643d82002-03-23 05:47:31 +0000237 except ValueError:
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000238 nbytes = 0
Guido van Rossum8cb65402002-02-01 16:27:59 +0000239 files = popenx(cmdline, 'b')
240 fi = files[0]
241 fo = files[1]
242 if self.have_popen3:
243 fe = files[2]
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000244 if self.command.lower() == "post" and nbytes > 0:
245 data = self.rfile.read(nbytes)
246 fi.write(data)
247 fi.close()
248 shutil.copyfileobj(fo, self.wfile)
Guido van Rossum8cb65402002-02-01 16:27:59 +0000249 if self.have_popen3:
250 errors = fe.read()
251 fe.close()
252 if errors:
253 self.log_error('%s', errors)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000254 sts = fo.close()
255 if sts:
256 self.log_error("CGI script exit status %#x", sts)
257 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000258 self.log_message("CGI script exited OK")
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000259
260 else:
261 # Other O.S. -- execute script in this process
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000262 save_argv = sys.argv
263 save_stdin = sys.stdin
264 save_stdout = sys.stdout
265 save_stderr = sys.stderr
266 try:
267 try:
268 sys.argv = [scriptfile]
269 if '=' not in decoded_query:
270 sys.argv.append(decoded_query)
271 sys.stdout = self.wfile
272 sys.stdin = self.rfile
273 execfile(scriptfile, {"__name__": "__main__"})
274 finally:
275 sys.argv = save_argv
276 sys.stdin = save_stdin
277 sys.stdout = save_stdout
278 sys.stderr = save_stderr
279 except SystemExit, sts:
280 self.log_error("CGI script exit status %s", str(sts))
281 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000282 self.log_message("CGI script exited OK")
Guido van Rossume7e578f1995-08-04 04:00:20 +0000283
284
285nobody = None
286
287def nobody_uid():
288 """Internal routine to get nobody's uid"""
289 global nobody
290 if nobody:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000291 return nobody
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000292 try:
293 import pwd
294 except ImportError:
295 return -1
Guido van Rossume7e578f1995-08-04 04:00:20 +0000296 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000297 nobody = pwd.getpwnam('nobody')[2]
Guido van Rossum630b8111999-04-28 12:21:47 +0000298 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000299 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
Guido van Rossume7e578f1995-08-04 04:00:20 +0000300 return nobody
301
302
303def executable(path):
304 """Test for executable file."""
305 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000306 st = os.stat(path)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000307 except os.error:
Guido van Rossum8ca162f2002-04-07 06:36:23 +0000308 return False
Raymond Hettinger32200ae2002-06-01 19:51:15 +0000309 return st.st_mode & 0111 != 0
Guido van Rossume7e578f1995-08-04 04:00:20 +0000310
311
312def test(HandlerClass = CGIHTTPRequestHandler,
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000313 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossume7e578f1995-08-04 04:00:20 +0000314 SimpleHTTPServer.test(HandlerClass, ServerClass)
315
316
317if __name__ == '__main__':
318 test()