Quick update to the README file. For intros and books we now point to
[python/dscho.git] / Lib / CGIHTTPServer.py
blobfa30cbdddae25c732b5c196ce4ac1592f947b2a4
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, this module will not work;
7 SystemError will be raised instead.
9 """
12 __version__ = "0.3"
15 import os
16 import string
17 import urllib
18 import BaseHTTPServer
19 import SimpleHTTPServer
22 try:
23 os.fork
24 except AttributeError:
25 raise SystemError, __name__ + " requires os.fork()"
28 class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
30 """Complete HTTP server with GET, HEAD and POST commands.
32 GET and HEAD also support running CGI scripts.
34 The POST command is *only* implemented for CGI scripts.
36 """
38 def do_POST(self):
39 """Serve a POST request.
41 This is only implemented for CGI scripts.
43 """
45 if self.is_cgi():
46 self.run_cgi()
47 else:
48 self.send_error(501, "Can only POST to CGI scripts")
50 def send_head(self):
51 """Version of send_head that support CGI scripts"""
52 if self.is_cgi():
53 return self.run_cgi()
54 else:
55 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
57 def is_cgi(self):
58 """test whether PATH corresponds to a CGI script.
60 Return a tuple (dir, rest) if PATH requires running a
61 CGI script, None if not. Note that rest begins with a
62 slash if it is not empty.
64 The default implementation tests whether the path
65 begins with one of the strings in the list
66 self.cgi_directories (and the next character is a '/'
67 or the end of the string).
69 """
71 path = self.path
73 for x in self.cgi_directories:
74 i = len(x)
75 if path[:i] == x and (not path[i:] or path[i] == '/'):
76 self.cgi_info = path[:i], path[i+1:]
77 return 1
78 return 0
80 cgi_directories = ['/cgi-bin', '/htbin']
82 def run_cgi(self):
83 """Execute a CGI script."""
84 dir, rest = self.cgi_info
85 i = string.rfind(rest, '?')
86 if i >= 0:
87 rest, query = rest[:i], rest[i+1:]
88 else:
89 query = ''
90 i = string.find(rest, '/')
91 if i >= 0:
92 script, rest = rest[:i], rest[i:]
93 else:
94 script, rest = rest, ''
95 scriptname = dir + '/' + script
96 scriptfile = self.translate_path(scriptname)
97 if not os.path.exists(scriptfile):
98 self.send_error(404, "No such CGI script (%s)" % `scriptname`)
99 return
100 if not os.path.isfile(scriptfile):
101 self.send_error(403, "CGI script is not a plain file (%s)" %
102 `scriptname`)
103 return
104 if not executable(scriptfile):
105 self.send_error(403, "CGI script is not executable (%s)" %
106 `scriptname`)
107 return
108 nobody = nobody_uid()
109 self.send_response(200, "Script output follows")
110 self.wfile.flush() # Always flush before forking
111 pid = os.fork()
112 if pid != 0:
113 # Parent
114 pid, sts = os.waitpid(pid, 0)
115 if sts:
116 self.log_error("CGI script exit status x%x" % sts)
117 return
118 # Child
119 try:
120 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
121 # XXX Much of the following could be prepared ahead of time!
122 env = {}
123 env['SERVER_SOFTWARE'] = self.version_string()
124 env['SERVER_NAME'] = self.server.server_name
125 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
126 env['SERVER_PROTOCOL'] = self.protocol_version
127 env['SERVER_PORT'] = str(self.server.server_port)
128 env['REQUEST_METHOD'] = self.command
129 uqrest = urllib.unquote(rest)
130 env['PATH_INFO'] = uqrest
131 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
132 env['SCRIPT_NAME'] = scriptname
133 if query:
134 env['QUERY_STRING'] = query
135 host = self.address_string()
136 if host != self.client_address[0]:
137 env['REMOTE_HOST'] = host
138 env['REMOTE_ADDR'] = self.client_address[0]
139 # AUTH_TYPE
140 # REMOTE_USER
141 # REMOTE_IDENT
142 if self.headers.typeheader is None:
143 env['CONTENT_TYPE'] = self.headers.type
144 else:
145 env['CONTENT_TYPE'] = self.headers.typeheader
146 length = self.headers.getheader('content-length')
147 if length:
148 env['CONTENT_LENGTH'] = length
149 accept = []
150 for line in self.headers.getallmatchingheaders('accept'):
151 if line[:1] in string.whitespace:
152 accept.append(string.strip(line))
153 else:
154 accept = accept + string.split(line[7:], ',')
155 env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
156 ua = self.headers.getheader('user-agent')
157 if ua:
158 env['HTTP_USER_AGENT'] = ua
159 co = filter(None, self.headers.getheaders('cookie'))
160 if co:
161 env['HTTP_COOKIE'] = string.join(co, ', ')
162 # XXX Other HTTP_* headers
163 decoded_query = string.replace(query, '+', ' ')
164 try:
165 os.setuid(nobody)
166 except os.error:
167 pass
168 os.dup2(self.rfile.fileno(), 0)
169 os.dup2(self.wfile.fileno(), 1)
170 print scriptfile, script, decoded_query
171 os.execve(scriptfile,
172 [script, decoded_query],
173 env)
174 except:
175 self.server.handle_error(self.request, self.client_address)
176 os._exit(127)
179 nobody = None
181 def nobody_uid():
182 """Internal routine to get nobody's uid"""
183 global nobody
184 if nobody:
185 return nobody
186 import pwd
187 try:
188 nobody = pwd.getpwnam('nobody')[2]
189 except KeyError:
190 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
191 return nobody
194 def executable(path):
195 """Test for executable file."""
196 try:
197 st = os.stat(path)
198 except os.error:
199 return 0
200 return st[0] & 0111 != 0
203 def test(HandlerClass = CGIHTTPRequestHandler,
204 ServerClass = BaseHTTPServer.HTTPServer):
205 SimpleHTTPServer.test(HandlerClass, ServerClass)
208 if __name__ == '__main__':
209 test()