Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e7d6b0a

Browse files
committed
An honest attempt to make this work on Unix, Windows, and even
Macintosh (the latter untested). This closes Bug #110839.
1 parent d9a8e96 commit e7d6b0a

1 file changed

Lines changed: 173 additions & 81 deletions

File tree

Lib/CGIHTTPServer.py

Lines changed: 173 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,31 @@
33
This module builds on SimpleHTTPServer by implementing GET and POST
44
requests to cgi-bin scripts.
55
6-
If the os.fork() function is not present, this module will not work;
7-
SystemError will be raised instead.
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.
10+
11+
In all cases, the implementation is intentionally naive -- all
12+
requests are executed sychronously.
13+
14+
SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
15+
-- it may execute arbitrary Python code or external programs.
816
917
"""
1018

1119

12-
__version__ = "0.3"
20+
__version__ = "0.4"
1321

1422

1523
import os
24+
import sys
1625
import string
1726
import urllib
1827
import BaseHTTPServer
1928
import SimpleHTTPServer
2029

2130

22-
try:
23-
os.fork
24-
except AttributeError:
25-
raise SystemError, __name__ + " requires os.fork()"
26-
27-
2831
class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
2932

3033
"""Complete HTTP server with GET, HEAD and POST commands.
@@ -35,6 +38,10 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
3538
3639
"""
3740

41+
# Determine platform specifics
42+
have_fork = hasattr(os, 'fork')
43+
have_popen2 = hasattr(os, 'popen2')
44+
3845
# Make rfile unbuffered -- we need to read one line and then pass
3946
# the rest to a subprocess, so we can't use buffered input.
4047
rbufsize = 0
@@ -59,9 +66,9 @@ def send_head(self):
5966
return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
6067

6168
def is_cgi(self):
62-
"""test whether PATH corresponds to a CGI script.
69+
"""Test whether self.path corresponds to a CGI script.
6370
64-
Return a tuple (dir, rest) if PATH requires running a
71+
Return a tuple (dir, rest) if self.path requires running a
6572
CGI script, None if not. Note that rest begins with a
6673
slash if it is not empty.
6774
@@ -83,6 +90,15 @@ def is_cgi(self):
8390

8491
cgi_directories = ['/cgi-bin', '/htbin']
8592

93+
def is_executable(self, path):
94+
"""Test whether argument path is an executable file."""
95+
return executable(path)
96+
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")
101+
86102
def run_cgi(self):
87103
"""Execute a CGI script."""
88104
dir, rest = self.cgi_info
@@ -105,79 +121,152 @@ def run_cgi(self):
105121
self.send_error(403, "CGI script is not a plain file (%s)" %
106122
`scriptname`)
107123
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()
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
134+
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 string.whitespace:
167+
accept.append(string.strip(line))
168+
else:
169+
accept = accept + string.split(line[7:], ',')
170+
env['HTTP_ACCEPT'] = string.joinfields(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'] = string.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, "")
184+
113185
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)
186+
187+
decoded_query = string.replace(query, '+', ' ')
188+
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)
215+
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 %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)
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()
119241
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
146-
if self.headers.typeheader is None:
147-
env['CONTENT_TYPE'] = self.headers.type
242+
self.log_error("CGI script exit status %#x", sts)
148243
else:
149-
env['CONTENT_TYPE'] = self.headers.typeheader
150-
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:
158-
accept = accept + string.split(line[7:], ',')
159-
env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
160-
ua = self.headers.getheader('user-agent')
161-
if ua:
162-
env['HTTP_USER_AGENT'] = ua
163-
co = filter(None, self.headers.getheaders('cookie'))
164-
if co:
165-
env['HTTP_COOKIE'] = string.join(co, ', ')
166-
# XXX Other HTTP_* headers
167-
decoded_query = string.replace(query, '+', ' ')
244+
self.log_error("CGI script exited OK")
245+
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
168253
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)
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")
181270

182271

183272
nobody = None
@@ -187,7 +276,10 @@ def nobody_uid():
187276
global nobody
188277
if nobody:
189278
return nobody
190-
import pwd
279+
try:
280+
import pwd
281+
except ImportError:
282+
return -1
191283
try:
192284
nobody = pwd.getpwnam('nobody')[2]
193285
except KeyError:

0 commit comments

Comments
 (0)