Kaydet (Commit) e7d6b0a2 authored tarafından Guido van Rossum's avatar Guido van Rossum

An honest attempt to make this work on Unix, Windows, and even

Macintosh (the latter untested).

This closes Bug #110839.
üst d9a8e965
...@@ -3,28 +3,31 @@ ...@@ -3,28 +3,31 @@
This module builds on SimpleHTTPServer by implementing GET and POST This module builds on SimpleHTTPServer by implementing GET and POST
requests to cgi-bin scripts. requests to cgi-bin scripts.
If the os.fork() function is not present, this module will not work; If the os.fork() function is not present (e.g. on Windows),
SystemError will be raised instead. os.popen2() is used as a fallback, with slightly altered semantics; if
that function is not present either (e.g. on Macintosh), only Python
scripts are supported, and they are executed by the current process.
In all cases, the implementation is intentionally naive -- all
requests are executed sychronously.
SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
-- it may execute arbitrary Python code or external programs.
""" """
__version__ = "0.3" __version__ = "0.4"
import os import os
import sys
import string import string
import urllib import urllib
import BaseHTTPServer import BaseHTTPServer
import SimpleHTTPServer import SimpleHTTPServer
try:
os.fork
except AttributeError:
raise SystemError, __name__ + " requires os.fork()"
class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
"""Complete HTTP server with GET, HEAD and POST commands. """Complete HTTP server with GET, HEAD and POST commands.
...@@ -35,6 +38,10 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): ...@@ -35,6 +38,10 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
""" """
# Determine platform specifics
have_fork = hasattr(os, 'fork')
have_popen2 = hasattr(os, 'popen2')
# Make rfile unbuffered -- we need to read one line and then pass # Make rfile unbuffered -- we need to read one line and then pass
# the rest to a subprocess, so we can't use buffered input. # the rest to a subprocess, so we can't use buffered input.
rbufsize = 0 rbufsize = 0
...@@ -59,9 +66,9 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): ...@@ -59,9 +66,9 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self) return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
def is_cgi(self): def is_cgi(self):
"""test whether PATH corresponds to a CGI script. """Test whether self.path corresponds to a CGI script.
Return a tuple (dir, rest) if PATH requires running a Return a tuple (dir, rest) if self.path requires running a
CGI script, None if not. Note that rest begins with a CGI script, None if not. Note that rest begins with a
slash if it is not empty. slash if it is not empty.
...@@ -83,6 +90,15 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): ...@@ -83,6 +90,15 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
cgi_directories = ['/cgi-bin', '/htbin'] cgi_directories = ['/cgi-bin', '/htbin']
def is_executable(self, path):
"""Test whether argument path is an executable file."""
return executable(path)
def is_python(self, path):
"""Test whether argument path is a Python script."""
head, tail = os.path.splitext(path)
return tail.lower() in (".py", ".pyw")
def run_cgi(self): def run_cgi(self):
"""Execute a CGI script.""" """Execute a CGI script."""
dir, rest = self.cgi_info dir, rest = self.cgi_info
...@@ -105,22 +121,17 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): ...@@ -105,22 +121,17 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
self.send_error(403, "CGI script is not a plain file (%s)" % self.send_error(403, "CGI script is not a plain file (%s)" %
`scriptname`) `scriptname`)
return return
if not executable(scriptfile): ispy = self.is_python(scriptname)
self.send_error(403, "CGI script is not executable (%s)" % if not ispy:
if not (self.have_fork or self.have_popen2):
self.send_error(403, "CGI script is not a Python script (%s)" %
`scriptname`) `scriptname`)
return return
nobody = nobody_uid() if not self.is_executable(scriptfile):
self.send_response(200, "Script output follows") self.send_error(403, "CGI script is not executable (%s)" %
self.wfile.flush() # Always flush before forking `scriptname`)
pid = os.fork()
if pid != 0:
# Parent
pid, sts = os.waitpid(pid, 0)
if sts:
self.log_error("CGI script exit status x%x" % sts)
return return
# Child
try:
# Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
# XXX Much of the following could be prepared ahead of time! # XXX Much of the following could be prepared ahead of time!
env = {} env = {}
...@@ -140,9 +151,9 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): ...@@ -140,9 +151,9 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
if host != self.client_address[0]: if host != self.client_address[0]:
env['REMOTE_HOST'] = host env['REMOTE_HOST'] = host
env['REMOTE_ADDR'] = self.client_address[0] env['REMOTE_ADDR'] = self.client_address[0]
# AUTH_TYPE # XXX AUTH_TYPE
# REMOTE_USER # XXX REMOTE_USER
# REMOTE_IDENT # XXX REMOTE_IDENT
if self.headers.typeheader is None: if self.headers.typeheader is None:
env['CONTENT_TYPE'] = self.headers.type env['CONTENT_TYPE'] = self.headers.type
else: else:
...@@ -164,21 +175,99 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): ...@@ -164,21 +175,99 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
if co: if co:
env['HTTP_COOKIE'] = string.join(co, ', ') env['HTTP_COOKIE'] = string.join(co, ', ')
# XXX Other HTTP_* headers # XXX Other HTTP_* headers
if not self.have_fork:
# Since we're setting the env in the parent, provide empty
# values to override previously set values
for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
'HTTP_USER_AGENT', 'HTTP_COOKIE'):
env.setdefault(k, "")
self.send_response(200, "Script output follows")
decoded_query = string.replace(query, '+', ' ') decoded_query = string.replace(query, '+', ' ')
if self.have_fork:
# Unix -- fork as we should
args = [script]
if '=' not in decoded_query:
args.append(decoded_query)
nobody = nobody_uid()
self.wfile.flush() # Always flush before forking
pid = os.fork()
if pid != 0:
# Parent
pid, sts = os.waitpid(pid, 0)
if sts:
self.log_error("CGI script exit status %#x", sts)
return
# Child
try:
try: try:
os.setuid(nobody) os.setuid(nobody)
except os.error: except os.error:
pass pass
os.dup2(self.rfile.fileno(), 0) os.dup2(self.rfile.fileno(), 0)
os.dup2(self.wfile.fileno(), 1) os.dup2(self.wfile.fileno(), 1)
print scriptfile, script, decoded_query os.execve(scriptfile, args, env)
os.execve(scriptfile,
[script, decoded_query],
env)
except: except:
self.server.handle_error(self.request, self.client_address) self.server.handle_error(self.request, self.client_address)
os._exit(127) os._exit(127)
elif self.have_popen2:
# Windows -- use popen2 to create a subprocess
import shutil
os.environ.update(env)
cmdline = scriptfile
if self.is_python(scriptfile):
interp = sys.executable
if interp.lower().endswith("w.exe"):
# On Windows, use python.exe, not python.exe
interp = interp[:-5] = interp[-4:]
cmdline = "%s %s" % (interp, cmdline)
if '=' not in query and '"' not in query:
cmdline = '%s "%s"' % (cmdline, query)
self.log_error("command: %s", cmdline)
try:
nbytes = int(length)
except:
nbytes = 0
fi, fo = os.popen2(cmdline)
if self.command.lower() == "post" and nbytes > 0:
data = self.rfile.read(nbytes)
fi.write(data)
fi.close()
shutil.copyfileobj(fo, self.wfile)
sts = fo.close()
if sts:
self.log_error("CGI script exit status %#x", sts)
else:
self.log_error("CGI script exited OK")
else:
# Other O.S. -- execute script in this process
os.environ.update(env)
save_argv = sys.argv
save_stdin = sys.stdin
save_stdout = sys.stdout
save_stderr = sys.stderr
try:
try:
sys.argv = [scriptfile]
if '=' not in decoded_query:
sys.argv.append(decoded_query)
sys.stdout = self.wfile
sys.stdin = self.rfile
execfile(scriptfile, {"__name__": "__main__"})
finally:
sys.argv = save_argv
sys.stdin = save_stdin
sys.stdout = save_stdout
sys.stderr = save_stderr
except SystemExit, sts:
self.log_error("CGI script exit status %s", str(sts))
else:
self.log_error("CGI script exited OK")
nobody = None nobody = None
...@@ -187,7 +276,10 @@ def nobody_uid(): ...@@ -187,7 +276,10 @@ def nobody_uid():
global nobody global nobody
if nobody: if nobody:
return nobody return nobody
try:
import pwd import pwd
except ImportError:
return -1
try: try:
nobody = pwd.getpwnam('nobody')[2] nobody = pwd.getpwnam('nobody')[2]
except KeyError: except KeyError:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment