This commit was manufactured by cvs2svn to create tag 'r22a4-fork'.
[python/dscho.git] / Lib / CGIHTTPServer.py
blob809cab73c8dc1d0dc9a28cc991d6788ac7375105
1 """CGI-savvy HTTP Server.
3 This module builds on SimpleHTTPServer by implementing GET and POST
4 requests to cgi-bin scripts.
6 If the os.fork() function is not present (e.g. on Windows),
7 os.popen2() is used as a fallback, with slightly altered semantics; if
8 that function is not present either (e.g. on Macintosh), only Python
9 scripts are supported, and they are executed by the current process.
11 In all cases, the implementation is intentionally naive -- all
12 requests are executed sychronously.
14 SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
15 -- it may execute arbitrary Python code or external programs.
17 """
20 __version__ = "0.4"
22 __all__ = ["CGIHTTPRequestHandler"]
24 import os
25 import sys
26 import urllib
27 import BaseHTTPServer
28 import SimpleHTTPServer
31 class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
33 """Complete HTTP server with GET, HEAD and POST commands.
35 GET and HEAD also support running CGI scripts.
37 The POST command is *only* implemented for CGI scripts.
39 """
41 # Determine platform specifics
42 have_fork = hasattr(os, 'fork')
43 have_popen2 = hasattr(os, 'popen2')
45 # 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
49 def do_POST(self):
50 """Serve a POST request.
52 This is only implemented for CGI scripts.
54 """
56 if self.is_cgi():
57 self.run_cgi()
58 else:
59 self.send_error(501, "Can only POST to CGI scripts")
61 def send_head(self):
62 """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)
68 def is_cgi(self):
69 """Test whether self.path corresponds to a CGI script.
71 Return a tuple (dir, rest) if self.path requires running a
72 CGI script, None if not. Note that rest begins with a
73 slash if it is not empty.
75 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).
80 """
82 path = self.path
84 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
91 cgi_directories = ['/cgi-bin', '/htbin']
93 def is_executable(self, path):
94 """Test whether argument path is an executable file."""
95 return executable(path)
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")
102 def run_cgi(self):
103 """Execute a CGI script."""
104 dir, rest = self.cgi_info
105 i = rest.rfind('?')
106 if i >= 0:
107 rest, query = rest[:i], rest[i+1:]
108 else:
109 query = ''
110 i = rest.find('/')
111 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
124 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
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'):
166 if line[:1] in "\t\n\r ":
167 accept.append(line.strip())
168 else:
169 accept = accept + line[7:].split(',')
170 env['HTTP_ACCEPT'] = ','.join(accept)
171 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:
176 env['HTTP_COOKIE'] = ', '.join(co)
177 # 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, "")
185 self.send_response(200, "Script output follows")
187 decoded_query = query.replace('+', ' ')
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()
195 self.wfile.flush() # Always flush before forking
196 pid = os.fork()
197 if pid != 0:
198 # Parent
199 pid, sts = os.waitpid(pid, 0)
200 if sts:
201 self.log_error("CGI script exit status %#x", sts)
202 return
203 # Child
204 try:
205 try:
206 os.setuid(nobody)
207 except os.error:
208 pass
209 os.dup2(self.rfile.fileno(), 0)
210 os.dup2(self.wfile.fileno(), 1)
211 os.execve(scriptfile, args, env)
212 except:
213 self.server.handle_error(self.request, self.client_address)
214 os._exit(127)
216 elif self.have_popen2:
217 # Windows -- use popen2 to create a subprocess
218 import shutil
219 os.environ.update(env)
220 cmdline = scriptfile
221 if self.is_python(scriptfile):
222 interp = sys.executable
223 if interp.lower().endswith("w.exe"):
224 # On Windows, use python.exe, not python.exe
225 interp = interp[:-5] = interp[-4:]
226 cmdline = "%s -u %s" % (interp, cmdline)
227 if '=' not in query and '"' not in query:
228 cmdline = '%s "%s"' % (cmdline, query)
229 self.log_error("command: %s", cmdline)
230 try:
231 nbytes = int(length)
232 except:
233 nbytes = 0
234 fi, fo = os.popen2(cmdline, 'b')
235 if self.command.lower() == "post" and nbytes > 0:
236 data = self.rfile.read(nbytes)
237 fi.write(data)
238 fi.close()
239 shutil.copyfileobj(fo, self.wfile)
240 sts = fo.close()
241 if sts:
242 self.log_error("CGI script exit status %#x", sts)
243 else:
244 self.log_error("CGI script exited OK")
246 else:
247 # Other O.S. -- execute script in this process
248 os.environ.update(env)
249 save_argv = sys.argv
250 save_stdin = sys.stdin
251 save_stdout = sys.stdout
252 save_stderr = sys.stderr
253 try:
254 try:
255 sys.argv = [scriptfile]
256 if '=' not in decoded_query:
257 sys.argv.append(decoded_query)
258 sys.stdout = self.wfile
259 sys.stdin = self.rfile
260 execfile(scriptfile, {"__name__": "__main__"})
261 finally:
262 sys.argv = save_argv
263 sys.stdin = save_stdin
264 sys.stdout = save_stdout
265 sys.stderr = save_stderr
266 except SystemExit, sts:
267 self.log_error("CGI script exit status %s", str(sts))
268 else:
269 self.log_error("CGI script exited OK")
272 nobody = None
274 def nobody_uid():
275 """Internal routine to get nobody's uid"""
276 global nobody
277 if nobody:
278 return nobody
279 try:
280 import pwd
281 except ImportError:
282 return -1
283 try:
284 nobody = pwd.getpwnam('nobody')[2]
285 except KeyError:
286 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
287 return nobody
290 def executable(path):
291 """Test for executable file."""
292 try:
293 st = os.stat(path)
294 except os.error:
295 return 0
296 return st[0] & 0111 != 0
299 def test(HandlerClass = CGIHTTPRequestHandler,
300 ServerClass = BaseHTTPServer.HTTPServer):
301 SimpleHTTPServer.test(HandlerClass, ServerClass)
304 if __name__ == '__main__':
305 test()