blob: e8abd22c754507cf6459fa9cd6c6dcc334e027b1 [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')
44
Guido van Rossum6aefd912000-09-01 03:27:34 +000045 # Make rfile unbuffered -- we need to read one line and then pass
46 # the rest to a subprocess, so we can't use buffered input.
47 rbufsize = 0
48
Guido van Rossume7e578f1995-08-04 04:00:20 +000049 def do_POST(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000050 """Serve a POST request.
Guido van Rossume7e578f1995-08-04 04:00:20 +000051
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000052 This is only implemented for CGI scripts.
Guido van Rossume7e578f1995-08-04 04:00:20 +000053
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000054 """
Guido van Rossume7e578f1995-08-04 04:00:20 +000055
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000056 if self.is_cgi():
57 self.run_cgi()
58 else:
59 self.send_error(501, "Can only POST to CGI scripts")
Guido van Rossume7e578f1995-08-04 04:00:20 +000060
61 def send_head(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000062 """Version of send_head that support CGI scripts"""
63 if self.is_cgi():
64 return self.run_cgi()
65 else:
66 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
Guido van Rossume7e578f1995-08-04 04:00:20 +000067
68 def is_cgi(self):
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000069 """Test whether self.path corresponds to a CGI script.
Guido van Rossume7e578f1995-08-04 04:00:20 +000070
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000071 Return a tuple (dir, rest) if self.path requires running a
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000072 CGI script, None if not. Note that rest begins with a
73 slash if it is not empty.
Guido van Rossume7e578f1995-08-04 04:00:20 +000074
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000075 The default implementation tests whether the path
76 begins with one of the strings in the list
77 self.cgi_directories (and the next character is a '/'
78 or the end of the string).
Guido van Rossume7e578f1995-08-04 04:00:20 +000079
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000080 """
Guido van Rossume7e578f1995-08-04 04:00:20 +000081
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000082 path = self.path
Guido van Rossume7e578f1995-08-04 04:00:20 +000083
Guido van Rossum45e2fbc1998-03-26 21:13:24 +000084 for x in self.cgi_directories:
85 i = len(x)
86 if path[:i] == x and (not path[i:] or path[i] == '/'):
87 self.cgi_info = path[:i], path[i+1:]
88 return 1
89 return 0
Guido van Rossume7e578f1995-08-04 04:00:20 +000090
91 cgi_directories = ['/cgi-bin', '/htbin']
92
Guido van Rossume7d6b0a2000-09-19 04:01:01 +000093 def is_executable(self, path):
94 """Test whether argument path is an executable file."""
95 return executable(path)
96
97 def is_python(self, path):
98 """Test whether argument path is a Python script."""
99 head, tail = os.path.splitext(path)
100 return tail.lower() in (".py", ".pyw")
101
Guido van Rossume7e578f1995-08-04 04:00:20 +0000102 def run_cgi(self):
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000103 """Execute a CGI script."""
104 dir, rest = self.cgi_info
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000105 i = rest.rfind('?')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000106 if i >= 0:
107 rest, query = rest[:i], rest[i+1:]
108 else:
109 query = ''
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000110 i = rest.find('/')
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000111 if i >= 0:
112 script, rest = rest[:i], rest[i:]
113 else:
114 script, rest = rest, ''
115 scriptname = dir + '/' + script
116 scriptfile = self.translate_path(scriptname)
117 if not os.path.exists(scriptfile):
118 self.send_error(404, "No such CGI script (%s)" % `scriptname`)
119 return
120 if not os.path.isfile(scriptfile):
121 self.send_error(403, "CGI script is not a plain file (%s)" %
122 `scriptname`)
123 return
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000124 ispy = self.is_python(scriptname)
125 if not ispy:
126 if not (self.have_fork or self.have_popen2):
127 self.send_error(403, "CGI script is not a Python script (%s)" %
128 `scriptname`)
129 return
130 if not self.is_executable(scriptfile):
131 self.send_error(403, "CGI script is not executable (%s)" %
132 `scriptname`)
133 return
134
135 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
136 # XXX Much of the following could be prepared ahead of time!
137 env = {}
138 env['SERVER_SOFTWARE'] = self.version_string()
139 env['SERVER_NAME'] = self.server.server_name
140 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
141 env['SERVER_PROTOCOL'] = self.protocol_version
142 env['SERVER_PORT'] = str(self.server.server_port)
143 env['REQUEST_METHOD'] = self.command
144 uqrest = urllib.unquote(rest)
145 env['PATH_INFO'] = uqrest
146 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
147 env['SCRIPT_NAME'] = scriptname
148 if query:
149 env['QUERY_STRING'] = query
150 host = self.address_string()
151 if host != self.client_address[0]:
152 env['REMOTE_HOST'] = host
153 env['REMOTE_ADDR'] = self.client_address[0]
154 # XXX AUTH_TYPE
155 # XXX REMOTE_USER
156 # XXX REMOTE_IDENT
157 if self.headers.typeheader is None:
158 env['CONTENT_TYPE'] = self.headers.type
159 else:
160 env['CONTENT_TYPE'] = self.headers.typeheader
161 length = self.headers.getheader('content-length')
162 if length:
163 env['CONTENT_LENGTH'] = length
164 accept = []
165 for line in self.headers.getallmatchingheaders('accept'):
Eric S. Raymond7e642e82001-02-09 12:10:26 +0000166 if line[:1] in "\t\n\r ":
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000167 accept.append(line.strip())
Guido van Rossum01fc65d1998-05-13 20:13:24 +0000168 else:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000169 accept = accept + line[7:].split(',')
170 env['HTTP_ACCEPT'] = ','.join(accept)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000171 ua = self.headers.getheader('user-agent')
172 if ua:
173 env['HTTP_USER_AGENT'] = ua
174 co = filter(None, self.headers.getheaders('cookie'))
175 if co:
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000176 env['HTTP_COOKIE'] = ', '.join(co)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000177 # XXX Other HTTP_* headers
178 if not self.have_fork:
179 # Since we're setting the env in the parent, provide empty
180 # values to override previously set values
181 for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
182 'HTTP_USER_AGENT', 'HTTP_COOKIE'):
183 env.setdefault(k, "")
184
185 self.send_response(200, "Script output follows")
186
Eric S. Raymond6b71e742001-02-09 08:56:30 +0000187 decoded_query = query.replace('+', ' ')
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000188
189 if self.have_fork:
190 # Unix -- fork as we should
191 args = [script]
192 if '=' not in decoded_query:
193 args.append(decoded_query)
194 nobody = nobody_uid()
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000195 self.rfile.flush() # Always flush before forking
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000196 self.wfile.flush() # Always flush before forking
197 pid = os.fork()
198 if pid != 0:
199 # Parent
200 pid, sts = os.waitpid(pid, 0)
201 if sts:
202 self.log_error("CGI script exit status %#x", sts)
203 return
204 # Child
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000205 try:
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000206 try:
207 os.setuid(nobody)
208 except os.error:
209 pass
210 os.dup2(self.rfile.fileno(), 0)
211 os.dup2(self.wfile.fileno(), 1)
212 os.execve(scriptfile, args, env)
213 except:
214 self.server.handle_error(self.request, self.client_address)
215 os._exit(127)
216
217 elif self.have_popen2:
218 # Windows -- use popen2 to create a subprocess
219 import shutil
220 os.environ.update(env)
221 cmdline = scriptfile
222 if self.is_python(scriptfile):
223 interp = sys.executable
224 if interp.lower().endswith("w.exe"):
225 # On Windows, use python.exe, not python.exe
226 interp = interp[:-5] = interp[-4:]
Guido van Rossum16fd3382001-08-07 19:55:10 +0000227 cmdline = "%s -u %s" % (interp, cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000228 if '=' not in query and '"' not in query:
229 cmdline = '%s "%s"' % (cmdline, query)
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000230 self.log_message("command: %s", cmdline)
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000231 try:
232 nbytes = int(length)
233 except:
234 nbytes = 0
Guido van Rossum16fd3382001-08-07 19:55:10 +0000235 fi, fo = os.popen2(cmdline, 'b')
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000236 if self.command.lower() == "post" and nbytes > 0:
237 data = self.rfile.read(nbytes)
238 fi.write(data)
239 fi.close()
240 shutil.copyfileobj(fo, self.wfile)
241 sts = fo.close()
242 if sts:
243 self.log_error("CGI script exit status %#x", sts)
244 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000245 self.log_message("CGI script exited OK")
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000246
247 else:
248 # Other O.S. -- execute script in this process
249 os.environ.update(env)
250 save_argv = sys.argv
251 save_stdin = sys.stdin
252 save_stdout = sys.stdout
253 save_stderr = sys.stderr
254 try:
255 try:
256 sys.argv = [scriptfile]
257 if '=' not in decoded_query:
258 sys.argv.append(decoded_query)
259 sys.stdout = self.wfile
260 sys.stdin = self.rfile
261 execfile(scriptfile, {"__name__": "__main__"})
262 finally:
263 sys.argv = save_argv
264 sys.stdin = save_stdin
265 sys.stdout = save_stdout
266 sys.stderr = save_stderr
267 except SystemExit, sts:
268 self.log_error("CGI script exit status %s", str(sts))
269 else:
Guido van Rossumbcbdc952001-10-17 06:45:56 +0000270 self.log_message("CGI script exited OK")
Guido van Rossume7e578f1995-08-04 04:00:20 +0000271
272
273nobody = None
274
275def nobody_uid():
276 """Internal routine to get nobody's uid"""
277 global nobody
278 if nobody:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000279 return nobody
Guido van Rossume7d6b0a2000-09-19 04:01:01 +0000280 try:
281 import pwd
282 except ImportError:
283 return -1
Guido van Rossume7e578f1995-08-04 04:00:20 +0000284 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000285 nobody = pwd.getpwnam('nobody')[2]
Guido van Rossum630b8111999-04-28 12:21:47 +0000286 except KeyError:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000287 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
Guido van Rossume7e578f1995-08-04 04:00:20 +0000288 return nobody
289
290
291def executable(path):
292 """Test for executable file."""
293 try:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000294 st = os.stat(path)
Guido van Rossume7e578f1995-08-04 04:00:20 +0000295 except os.error:
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000296 return 0
Guido van Rossum13ad35a1996-01-25 18:23:50 +0000297 return st[0] & 0111 != 0
Guido van Rossume7e578f1995-08-04 04:00:20 +0000298
299
300def test(HandlerClass = CGIHTTPRequestHandler,
Guido van Rossum45e2fbc1998-03-26 21:13:24 +0000301 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossume7e578f1995-08-04 04:00:20 +0000302 SimpleHTTPServer.test(HandlerClass, ServerClass)
303
304
305if __name__ == '__main__':
306 test()