blob: 6a259a32daafc67b08a014f52702307375c5a3df [file] [log] [blame]
Guido van Rossum5c971671996-07-22 15:23:25 +00001"""CGI-savvy HTTP Server.
2
3This module builds on SimpleHTTPServer by implementing GET and POST
4requests to cgi-bin scripts.
5
Guido van Rossumaad67612000-05-08 17:31:04 +00006If the os.fork() function is not present, this module will not work;
7SystemError will be raised instead.
8
Guido van Rossum5c971671996-07-22 15:23:25 +00009"""
10
11
12__version__ = "0.3"
13
14
15import os
Guido van Rossum5c971671996-07-22 15:23:25 +000016import string
17import urllib
18import BaseHTTPServer
19import SimpleHTTPServer
20
21
Guido van Rossumaad67612000-05-08 17:31:04 +000022try:
23 os.fork
24except AttributeError:
25 raise SystemError, __name__ + " requires os.fork()"
26
27
Guido van Rossum5c971671996-07-22 15:23:25 +000028class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
29
30 """Complete HTTP server with GET, HEAD and POST commands.
31
32 GET and HEAD also support running CGI scripts.
33
34 The POST command is *only* implemented for CGI scripts.
35
36 """
37
Guido van Rossum8d691c82000-09-01 19:25:51 +000038 # Make rfile unbuffered -- we need to read one line and then pass
39 # the rest to a subprocess, so we can't use buffered input.
40 rbufsize = 0
41
Guido van Rossum5c971671996-07-22 15:23:25 +000042 def do_POST(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000043 """Serve a POST request.
Guido van Rossum5c971671996-07-22 15:23:25 +000044
Guido van Rossum548703a1998-03-26 22:14:20 +000045 This is only implemented for CGI scripts.
Guido van Rossum5c971671996-07-22 15:23:25 +000046
Guido van Rossum548703a1998-03-26 22:14:20 +000047 """
Guido van Rossum5c971671996-07-22 15:23:25 +000048
Guido van Rossum548703a1998-03-26 22:14:20 +000049 if self.is_cgi():
50 self.run_cgi()
51 else:
52 self.send_error(501, "Can only POST to CGI scripts")
Guido van Rossum5c971671996-07-22 15:23:25 +000053
54 def send_head(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000055 """Version of send_head that support CGI scripts"""
56 if self.is_cgi():
57 return self.run_cgi()
58 else:
59 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
Guido van Rossum5c971671996-07-22 15:23:25 +000060
61 def is_cgi(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000062 """test whether PATH corresponds to a CGI script.
Guido van Rossum5c971671996-07-22 15:23:25 +000063
Guido van Rossum548703a1998-03-26 22:14:20 +000064 Return a tuple (dir, rest) if PATH requires running a
65 CGI script, None if not. Note that rest begins with a
66 slash if it is not empty.
Guido van Rossum5c971671996-07-22 15:23:25 +000067
Guido van Rossum548703a1998-03-26 22:14:20 +000068 The default implementation tests whether the path
69 begins with one of the strings in the list
70 self.cgi_directories (and the next character is a '/'
71 or the end of the string).
Guido van Rossum5c971671996-07-22 15:23:25 +000072
Guido van Rossum548703a1998-03-26 22:14:20 +000073 """
Guido van Rossum5c971671996-07-22 15:23:25 +000074
Guido van Rossum548703a1998-03-26 22:14:20 +000075 path = self.path
Guido van Rossum5c971671996-07-22 15:23:25 +000076
Guido van Rossum548703a1998-03-26 22:14:20 +000077 for x in self.cgi_directories:
78 i = len(x)
79 if path[:i] == x and (not path[i:] or path[i] == '/'):
80 self.cgi_info = path[:i], path[i+1:]
81 return 1
82 return 0
Guido van Rossum5c971671996-07-22 15:23:25 +000083
84 cgi_directories = ['/cgi-bin', '/htbin']
85
86 def run_cgi(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000087 """Execute a CGI script."""
88 dir, rest = self.cgi_info
89 i = string.rfind(rest, '?')
90 if i >= 0:
91 rest, query = rest[:i], rest[i+1:]
92 else:
93 query = ''
94 i = string.find(rest, '/')
95 if i >= 0:
96 script, rest = rest[:i], rest[i:]
97 else:
98 script, rest = rest, ''
99 scriptname = dir + '/' + script
100 scriptfile = self.translate_path(scriptname)
101 if not os.path.exists(scriptfile):
102 self.send_error(404, "No such CGI script (%s)" % `scriptname`)
103 return
104 if not os.path.isfile(scriptfile):
105 self.send_error(403, "CGI script is not a plain file (%s)" %
106 `scriptname`)
107 return
108 if not executable(scriptfile):
109 self.send_error(403, "CGI script is not executable (%s)" %
110 `scriptname`)
111 return
112 nobody = nobody_uid()
113 self.send_response(200, "Script output follows")
114 self.wfile.flush() # Always flush before forking
115 pid = os.fork()
116 if pid != 0:
117 # Parent
118 pid, sts = os.waitpid(pid, 0)
119 if sts:
120 self.log_error("CGI script exit status x%x" % sts)
121 return
122 # Child
123 try:
124 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
125 # XXX Much of the following could be prepared ahead of time!
126 env = {}
127 env['SERVER_SOFTWARE'] = self.version_string()
128 env['SERVER_NAME'] = self.server.server_name
129 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
130 env['SERVER_PROTOCOL'] = self.protocol_version
131 env['SERVER_PORT'] = str(self.server.server_port)
132 env['REQUEST_METHOD'] = self.command
133 uqrest = urllib.unquote(rest)
134 env['PATH_INFO'] = uqrest
135 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
136 env['SCRIPT_NAME'] = scriptname
137 if query:
138 env['QUERY_STRING'] = query
139 host = self.address_string()
140 if host != self.client_address[0]:
141 env['REMOTE_HOST'] = host
142 env['REMOTE_ADDR'] = self.client_address[0]
143 # AUTH_TYPE
144 # REMOTE_USER
145 # REMOTE_IDENT
Guido van Rossume03c0501998-08-12 02:38:11 +0000146 if self.headers.typeheader is None:
147 env['CONTENT_TYPE'] = self.headers.type
148 else:
149 env['CONTENT_TYPE'] = self.headers.typeheader
Guido van Rossum548703a1998-03-26 22:14:20 +0000150 length = self.headers.getheader('content-length')
151 if length:
152 env['CONTENT_LENGTH'] = length
153 accept = []
154 for line in self.headers.getallmatchingheaders('accept'):
155 if line[:1] in string.whitespace:
156 accept.append(string.strip(line))
157 else:
Guido van Rossum7ea1d971998-12-22 13:50:33 +0000158 accept = accept + string.split(line[7:], ',')
Guido van Rossum548703a1998-03-26 22:14:20 +0000159 env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
160 ua = self.headers.getheader('user-agent')
161 if ua:
162 env['HTTP_USER_AGENT'] = ua
Guido van Rossumaad67612000-05-08 17:31:04 +0000163 co = filter(None, self.headers.getheaders('cookie'))
164 if co:
165 env['HTTP_COOKIE'] = string.join(co, ', ')
Guido van Rossum548703a1998-03-26 22:14:20 +0000166 # XXX Other HTTP_* headers
167 decoded_query = string.replace(query, '+', ' ')
168 try:
169 os.setuid(nobody)
170 except os.error:
171 pass
172 os.dup2(self.rfile.fileno(), 0)
173 os.dup2(self.wfile.fileno(), 1)
174 print scriptfile, script, decoded_query
175 os.execve(scriptfile,
176 [script, decoded_query],
177 env)
178 except:
179 self.server.handle_error(self.request, self.client_address)
180 os._exit(127)
Guido van Rossum5c971671996-07-22 15:23:25 +0000181
182
183nobody = None
184
185def nobody_uid():
186 """Internal routine to get nobody's uid"""
187 global nobody
188 if nobody:
Guido van Rossum548703a1998-03-26 22:14:20 +0000189 return nobody
Guido van Rossum5c971671996-07-22 15:23:25 +0000190 import pwd
191 try:
Guido van Rossum548703a1998-03-26 22:14:20 +0000192 nobody = pwd.getpwnam('nobody')[2]
Guido van Rossumaad67612000-05-08 17:31:04 +0000193 except KeyError:
Guido van Rossum548703a1998-03-26 22:14:20 +0000194 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
Guido van Rossum5c971671996-07-22 15:23:25 +0000195 return nobody
196
197
198def executable(path):
199 """Test for executable file."""
200 try:
Guido van Rossum548703a1998-03-26 22:14:20 +0000201 st = os.stat(path)
Guido van Rossum5c971671996-07-22 15:23:25 +0000202 except os.error:
Guido van Rossum548703a1998-03-26 22:14:20 +0000203 return 0
Guido van Rossum5c971671996-07-22 15:23:25 +0000204 return st[0] & 0111 != 0
205
206
207def test(HandlerClass = CGIHTTPRequestHandler,
Guido van Rossum548703a1998-03-26 22:14:20 +0000208 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossum5c971671996-07-22 15:23:25 +0000209 SimpleHTTPServer.test(HandlerClass, ServerClass)
210
211
212if __name__ == '__main__':
213 test()