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.
22 __all__
= ["CGIHTTPRequestHandler"]
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.
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.
50 """Serve a POST request.
52 This is only implemented for CGI scripts.
59 self
.send_error(501, "Can only POST to CGI scripts")
62 """Version of send_head that support CGI scripts"""
66 return SimpleHTTPServer
.SimpleHTTPRequestHandler
.send_head(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).
84 for x
in self
.cgi_directories
:
86 if path
[:i
] == x
and (not path
[i
:] or path
[i
] == '/'):
87 self
.cgi_info
= path
[:i
], path
[i
+1:]
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")
103 """Execute a CGI script."""
104 dir, rest
= self
.cgi_info
107 rest
, query
= rest
[:i
], rest
[i
+1:]
112 script
, rest
= rest
[:i
], rest
[i
:]
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`
)
120 if not os
.path
.isfile(scriptfile
):
121 self
.send_error(403, "CGI script is not a plain file (%s)" %
124 ispy
= self
.is_python(scriptname
)
126 if not (self
.have_fork
or self
.have_popen2
):
127 self
.send_error(403, "CGI script is not a Python script (%s)" %
130 if not self
.is_executable(scriptfile
):
131 self
.send_error(403, "CGI script is not executable (%s)" %
135 # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
136 # XXX Much of the following could be prepared ahead of time!
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
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]
157 if self
.headers
.typeheader
is None:
158 env
['CONTENT_TYPE'] = self
.headers
.type
160 env
['CONTENT_TYPE'] = self
.headers
.typeheader
161 length
= self
.headers
.getheader('content-length')
163 env
['CONTENT_LENGTH'] = length
165 for line
in self
.headers
.getallmatchingheaders('accept'):
166 if line
[:1] in "\t\n\r ":
167 accept
.append(line
.strip())
169 accept
= accept
+ line
[7:].split(',')
170 env
['HTTP_ACCEPT'] = ','.join(accept
)
171 ua
= self
.headers
.getheader('user-agent')
173 env
['HTTP_USER_AGENT'] = ua
174 co
= filter(None, self
.headers
.getheaders('cookie'))
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('+', ' ')
190 # Unix -- fork as we should
192 if '=' not in decoded_query
:
193 args
.append(decoded_query
)
194 nobody
= nobody_uid()
195 self
.wfile
.flush() # Always flush before forking
199 pid
, sts
= os
.waitpid(pid
, 0)
201 self
.log_error("CGI script exit status %#x", sts
)
209 os
.dup2(self
.rfile
.fileno(), 0)
210 os
.dup2(self
.wfile
.fileno(), 1)
211 os
.execve(scriptfile
, args
, env
)
213 self
.server
.handle_error(self
.request
, self
.client_address
)
216 elif self
.have_popen2
:
217 # Windows -- use popen2 to create a subprocess
219 os
.environ
.update(env
)
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
)
234 fi
, fo
= os
.popen2(cmdline
, 'b')
235 if self
.command
.lower() == "post" and nbytes
> 0:
236 data
= self
.rfile
.read(nbytes
)
239 shutil
.copyfileobj(fo
, self
.wfile
)
242 self
.log_error("CGI script exit status %#x", sts
)
244 self
.log_error("CGI script exited OK")
247 # Other O.S. -- execute script in this process
248 os
.environ
.update(env
)
250 save_stdin
= sys
.stdin
251 save_stdout
= sys
.stdout
252 save_stderr
= sys
.stderr
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__"})
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
))
269 self
.log_error("CGI script exited OK")
275 """Internal routine to get nobody's uid"""
284 nobody
= pwd
.getpwnam('nobody')[2]
286 nobody
= 1 + max(map(lambda x
: x
[2], pwd
.getpwall()))
290 def executable(path
):
291 """Test for executable file."""
296 return st
[0] & 0111 != 0
299 def test(HandlerClass
= CGIHTTPRequestHandler
,
300 ServerClass
= BaseHTTPServer
.HTTPServer
):
301 SimpleHTTPServer
.test(HandlerClass
, ServerClass
)
304 if __name__
== '__main__':