blob: 24bdeef7974594a1f1eebf4a892fe4a87b7eded2 [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
6"""
7
8
9__version__ = "0.3"
10
11
12import os
13import sys
14import time
15import socket
16import string
17import urllib
18import BaseHTTPServer
19import SimpleHTTPServer
20
21
22class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
23
24 """Complete HTTP server with GET, HEAD and POST commands.
25
26 GET and HEAD also support running CGI scripts.
27
28 The POST command is *only* implemented for CGI scripts.
29
30 """
31
32 def do_POST(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000033 """Serve a POST request.
Guido van Rossum5c971671996-07-22 15:23:25 +000034
Guido van Rossum548703a1998-03-26 22:14:20 +000035 This is only implemented for CGI scripts.
Guido van Rossum5c971671996-07-22 15:23:25 +000036
Guido van Rossum548703a1998-03-26 22:14:20 +000037 """
Guido van Rossum5c971671996-07-22 15:23:25 +000038
Guido van Rossum548703a1998-03-26 22:14:20 +000039 if self.is_cgi():
40 self.run_cgi()
41 else:
42 self.send_error(501, "Can only POST to CGI scripts")
Guido van Rossum5c971671996-07-22 15:23:25 +000043
44 def send_head(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000045 """Version of send_head that support CGI scripts"""
46 if self.is_cgi():
47 return self.run_cgi()
48 else:
49 return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
Guido van Rossum5c971671996-07-22 15:23:25 +000050
51 def is_cgi(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000052 """test whether PATH corresponds to a CGI script.
Guido van Rossum5c971671996-07-22 15:23:25 +000053
Guido van Rossum548703a1998-03-26 22:14:20 +000054 Return a tuple (dir, rest) if PATH requires running a
55 CGI script, None if not. Note that rest begins with a
56 slash if it is not empty.
Guido van Rossum5c971671996-07-22 15:23:25 +000057
Guido van Rossum548703a1998-03-26 22:14:20 +000058 The default implementation tests whether the path
59 begins with one of the strings in the list
60 self.cgi_directories (and the next character is a '/'
61 or the end of the string).
Guido van Rossum5c971671996-07-22 15:23:25 +000062
Guido van Rossum548703a1998-03-26 22:14:20 +000063 """
Guido van Rossum5c971671996-07-22 15:23:25 +000064
Guido van Rossum548703a1998-03-26 22:14:20 +000065 path = self.path
Guido van Rossum5c971671996-07-22 15:23:25 +000066
Guido van Rossum548703a1998-03-26 22:14:20 +000067 for x in self.cgi_directories:
68 i = len(x)
69 if path[:i] == x and (not path[i:] or path[i] == '/'):
70 self.cgi_info = path[:i], path[i+1:]
71 return 1
72 return 0
Guido van Rossum5c971671996-07-22 15:23:25 +000073
74 cgi_directories = ['/cgi-bin', '/htbin']
75
76 def run_cgi(self):
Guido van Rossum548703a1998-03-26 22:14:20 +000077 """Execute a CGI script."""
78 dir, rest = self.cgi_info
79 i = string.rfind(rest, '?')
80 if i >= 0:
81 rest, query = rest[:i], rest[i+1:]
82 else:
83 query = ''
84 i = string.find(rest, '/')
85 if i >= 0:
86 script, rest = rest[:i], rest[i:]
87 else:
88 script, rest = rest, ''
89 scriptname = dir + '/' + script
90 scriptfile = self.translate_path(scriptname)
91 if not os.path.exists(scriptfile):
92 self.send_error(404, "No such CGI script (%s)" % `scriptname`)
93 return
94 if not os.path.isfile(scriptfile):
95 self.send_error(403, "CGI script is not a plain file (%s)" %
96 `scriptname`)
97 return
98 if not executable(scriptfile):
99 self.send_error(403, "CGI script is not executable (%s)" %
100 `scriptname`)
101 return
102 nobody = nobody_uid()
103 self.send_response(200, "Script output follows")
104 self.wfile.flush() # Always flush before forking
105 pid = os.fork()
106 if pid != 0:
107 # Parent
108 pid, sts = os.waitpid(pid, 0)
109 if sts:
110 self.log_error("CGI script exit status x%x" % sts)
111 return
112 # Child
113 try:
114 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
115 # XXX Much of the following could be prepared ahead of time!
116 env = {}
117 env['SERVER_SOFTWARE'] = self.version_string()
118 env['SERVER_NAME'] = self.server.server_name
119 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
120 env['SERVER_PROTOCOL'] = self.protocol_version
121 env['SERVER_PORT'] = str(self.server.server_port)
122 env['REQUEST_METHOD'] = self.command
123 uqrest = urllib.unquote(rest)
124 env['PATH_INFO'] = uqrest
125 env['PATH_TRANSLATED'] = self.translate_path(uqrest)
126 env['SCRIPT_NAME'] = scriptname
127 if query:
128 env['QUERY_STRING'] = query
129 host = self.address_string()
130 if host != self.client_address[0]:
131 env['REMOTE_HOST'] = host
132 env['REMOTE_ADDR'] = self.client_address[0]
133 # AUTH_TYPE
134 # REMOTE_USER
135 # REMOTE_IDENT
Guido van Rossume03c0501998-08-12 02:38:11 +0000136 if self.headers.typeheader is None:
137 env['CONTENT_TYPE'] = self.headers.type
138 else:
139 env['CONTENT_TYPE'] = self.headers.typeheader
Guido van Rossum548703a1998-03-26 22:14:20 +0000140 length = self.headers.getheader('content-length')
141 if length:
142 env['CONTENT_LENGTH'] = length
143 accept = []
144 for line in self.headers.getallmatchingheaders('accept'):
145 if line[:1] in string.whitespace:
146 accept.append(string.strip(line))
147 else:
148 accept = accept + string.split(line[7:])
149 env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
150 ua = self.headers.getheader('user-agent')
151 if ua:
152 env['HTTP_USER_AGENT'] = ua
153 # XXX Other HTTP_* headers
154 decoded_query = string.replace(query, '+', ' ')
155 try:
156 os.setuid(nobody)
157 except os.error:
158 pass
159 os.dup2(self.rfile.fileno(), 0)
160 os.dup2(self.wfile.fileno(), 1)
161 print scriptfile, script, decoded_query
162 os.execve(scriptfile,
163 [script, decoded_query],
164 env)
165 except:
166 self.server.handle_error(self.request, self.client_address)
167 os._exit(127)
Guido van Rossum5c971671996-07-22 15:23:25 +0000168
169
170nobody = None
171
172def nobody_uid():
173 """Internal routine to get nobody's uid"""
174 global nobody
175 if nobody:
Guido van Rossum548703a1998-03-26 22:14:20 +0000176 return nobody
Guido van Rossum5c971671996-07-22 15:23:25 +0000177 import pwd
178 try:
Guido van Rossum548703a1998-03-26 22:14:20 +0000179 nobody = pwd.getpwnam('nobody')[2]
Guido van Rossum5c971671996-07-22 15:23:25 +0000180 except pwd.error:
Guido van Rossum548703a1998-03-26 22:14:20 +0000181 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
Guido van Rossum5c971671996-07-22 15:23:25 +0000182 return nobody
183
184
185def executable(path):
186 """Test for executable file."""
187 try:
Guido van Rossum548703a1998-03-26 22:14:20 +0000188 st = os.stat(path)
Guido van Rossum5c971671996-07-22 15:23:25 +0000189 except os.error:
Guido van Rossum548703a1998-03-26 22:14:20 +0000190 return 0
Guido van Rossum5c971671996-07-22 15:23:25 +0000191 return st[0] & 0111 != 0
192
193
194def test(HandlerClass = CGIHTTPRequestHandler,
Guido van Rossum548703a1998-03-26 22:14:20 +0000195 ServerClass = BaseHTTPServer.HTTPServer):
Guido van Rossum5c971671996-07-22 15:23:25 +0000196 SimpleHTTPServer.test(HandlerClass, ServerClass)
197
198
199if __name__ == '__main__':
200 test()