mirror of
https://github.com/python/cpython.git
synced 2024-11-29 21:05:33 +08:00
9fe394c1be
svn+ssh://pythondev@svn.python.org/python/trunk ........ r53545 | andrew.kuchling | 2007-01-24 21:06:41 +0100 (Wed, 24 Jan 2007) | 1 line Strengthen warning about using lock() ........ r53556 | thomas.heller | 2007-01-25 19:34:14 +0100 (Thu, 25 Jan 2007) | 3 lines Fix for #1643874: When calling SysAllocString, create a PyCObject which will eventually call SysFreeString to free the BSTR resource. ........ r53563 | andrew.kuchling | 2007-01-25 21:02:13 +0100 (Thu, 25 Jan 2007) | 1 line Add item ........ r53564 | brett.cannon | 2007-01-25 21:22:02 +0100 (Thu, 25 Jan 2007) | 8 lines Fix time.strptime's %U support. Basically rewrote the algorithm to be more generic so that one only has to shift certain values based on whether the week was specified to start on Monday or Sunday. Cut out a lot of edge case code compared to the previous version. Also broke algorithm out into its own function (that is private to the module). Fixes bug #1643943 (thanks Biran Nahas for the report). ........ r53570 | brett.cannon | 2007-01-26 00:30:39 +0100 (Fri, 26 Jan 2007) | 4 lines Remove specific mention of my name and email address from modules. Not really needed and all bug reports should go to the bug tracker, not directly to me. Plus I am not the only person to have edited these files at this point. ........ r53573 | fred.drake | 2007-01-26 17:28:44 +0100 (Fri, 26 Jan 2007) | 1 line fix typo (extraneous ")") ........ r53575 | georg.brandl | 2007-01-27 18:43:02 +0100 (Sat, 27 Jan 2007) | 4 lines Patch #1638243: the compiler package is now able to correctly compile a with statement; previously, executing code containing a with statement compiled by the compiler package crashed the interpreter. ........ r53578 | georg.brandl | 2007-01-27 18:59:42 +0100 (Sat, 27 Jan 2007) | 3 lines Patch #1634778: add missing encoding aliases for iso8859_15 and iso8859_16. ........ r53579 | georg.brandl | 2007-01-27 20:38:50 +0100 (Sat, 27 Jan 2007) | 2 lines Bug #1645944: os.access now returns bool but docstring is not updated ........ r53590 | brett.cannon | 2007-01-28 21:58:00 +0100 (Sun, 28 Jan 2007) | 2 lines Use the thread lock's context manager instead of a try/finally statement. ........ r53591 | brett.cannon | 2007-01-29 05:41:44 +0100 (Mon, 29 Jan 2007) | 2 lines Add a test for slicing an exception. ........ r53594 | andrew.kuchling | 2007-01-29 21:21:43 +0100 (Mon, 29 Jan 2007) | 1 line Minor edits to the curses HOWTO ........ r53596 | andrew.kuchling | 2007-01-29 21:55:40 +0100 (Mon, 29 Jan 2007) | 1 line Various minor edits ........ r53597 | andrew.kuchling | 2007-01-29 22:28:48 +0100 (Mon, 29 Jan 2007) | 1 line More edits ........ r53601 | tim.peters | 2007-01-30 04:03:46 +0100 (Tue, 30 Jan 2007) | 2 lines Whitespace normalization. ........ r53603 | georg.brandl | 2007-01-30 21:21:30 +0100 (Tue, 30 Jan 2007) | 2 lines Bug #1648191: typo in docs. ........ r53605 | brett.cannon | 2007-01-30 22:34:36 +0100 (Tue, 30 Jan 2007) | 8 lines No more raising of string exceptions! The next step of PEP 352 (for 2.6) causes raising a string exception to trigger a TypeError. Trying to catch a string exception raises a DeprecationWarning. References to string exceptions has been removed from the docs since they are now just an error. ........ r53618 | raymond.hettinger | 2007-02-01 22:02:59 +0100 (Thu, 01 Feb 2007) | 1 line Bug #1648179: set.update() not recognizing __iter__ overrides in dict subclasses. ........
363 lines
12 KiB
Python
363 lines
12 KiB
Python
"""CGI-savvy HTTP Server.
|
|
|
|
This module builds on SimpleHTTPServer by implementing GET and POST
|
|
requests to cgi-bin scripts.
|
|
|
|
If the os.fork() function is not present (e.g. on Windows),
|
|
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.
|
|
|
|
Note that status code 200 is sent prior to execution of a CGI script, so
|
|
scripts cannot send other status codes such as 302 (redirect).
|
|
"""
|
|
|
|
|
|
__version__ = "0.4"
|
|
|
|
__all__ = ["CGIHTTPRequestHandler"]
|
|
|
|
import os
|
|
import sys
|
|
import urllib
|
|
import BaseHTTPServer
|
|
import SimpleHTTPServer
|
|
import select
|
|
|
|
|
|
class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
|
|
|
"""Complete HTTP server with GET, HEAD and POST commands.
|
|
|
|
GET and HEAD also support running CGI scripts.
|
|
|
|
The POST command is *only* implemented for CGI scripts.
|
|
|
|
"""
|
|
|
|
# Determine platform specifics
|
|
have_fork = hasattr(os, 'fork')
|
|
have_popen2 = hasattr(os, 'popen2')
|
|
have_popen3 = hasattr(os, 'popen3')
|
|
|
|
# Make rfile unbuffered -- we need to read one line and then pass
|
|
# the rest to a subprocess, so we can't use buffered input.
|
|
rbufsize = 0
|
|
|
|
def do_POST(self):
|
|
"""Serve a POST request.
|
|
|
|
This is only implemented for CGI scripts.
|
|
|
|
"""
|
|
|
|
if self.is_cgi():
|
|
self.run_cgi()
|
|
else:
|
|
self.send_error(501, "Can only POST to CGI scripts")
|
|
|
|
def send_head(self):
|
|
"""Version of send_head that support CGI scripts"""
|
|
if self.is_cgi():
|
|
return self.run_cgi()
|
|
else:
|
|
return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
|
|
|
|
def is_cgi(self):
|
|
"""Test whether self.path corresponds to a CGI script.
|
|
|
|
Return a tuple (dir, rest) if self.path requires running a
|
|
CGI script, None if not. Note that rest begins with a
|
|
slash if it is not empty.
|
|
|
|
The default implementation tests whether the path
|
|
begins with one of the strings in the list
|
|
self.cgi_directories (and the next character is a '/'
|
|
or the end of the string).
|
|
|
|
"""
|
|
|
|
path = self.path
|
|
|
|
for x in self.cgi_directories:
|
|
i = len(x)
|
|
if path[:i] == x and (not path[i:] or path[i] == '/'):
|
|
self.cgi_info = path[:i], path[i+1:]
|
|
return True
|
|
return False
|
|
|
|
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):
|
|
"""Execute a CGI script."""
|
|
path = self.path
|
|
dir, rest = self.cgi_info
|
|
|
|
i = path.find('/', len(dir) + 1)
|
|
while i >= 0:
|
|
nextdir = path[:i]
|
|
nextrest = path[i+1:]
|
|
|
|
scriptdir = self.translate_path(nextdir)
|
|
if os.path.isdir(scriptdir):
|
|
dir, rest = nextdir, nextrest
|
|
i = path.find('/', len(dir) + 1)
|
|
else:
|
|
break
|
|
|
|
# find an explicit query string, if present.
|
|
i = rest.rfind('?')
|
|
if i >= 0:
|
|
rest, query = rest[:i], rest[i+1:]
|
|
else:
|
|
query = ''
|
|
|
|
# dissect the part after the directory name into a script name &
|
|
# a possible additional path, to be stored in PATH_INFO.
|
|
i = rest.find('/')
|
|
if i >= 0:
|
|
script, rest = rest[:i], rest[i:]
|
|
else:
|
|
script, rest = rest, ''
|
|
|
|
scriptname = dir + '/' + script
|
|
scriptfile = self.translate_path(scriptname)
|
|
if not os.path.exists(scriptfile):
|
|
self.send_error(404, "No such CGI script (%r)" % scriptname)
|
|
return
|
|
if not os.path.isfile(scriptfile):
|
|
self.send_error(403, "CGI script is not a plain file (%r)" %
|
|
scriptname)
|
|
return
|
|
ispy = self.is_python(scriptname)
|
|
if not ispy:
|
|
if not (self.have_fork or self.have_popen2 or self.have_popen3):
|
|
self.send_error(403, "CGI script is not a Python script (%r)" %
|
|
scriptname)
|
|
return
|
|
if not self.is_executable(scriptfile):
|
|
self.send_error(403, "CGI script is not executable (%r)" %
|
|
scriptname)
|
|
return
|
|
|
|
# Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
|
|
# XXX Much of the following could be prepared ahead of time!
|
|
env = {}
|
|
env['SERVER_SOFTWARE'] = self.version_string()
|
|
env['SERVER_NAME'] = self.server.server_name
|
|
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
|
|
env['SERVER_PROTOCOL'] = self.protocol_version
|
|
env['SERVER_PORT'] = str(self.server.server_port)
|
|
env['REQUEST_METHOD'] = self.command
|
|
uqrest = urllib.unquote(rest)
|
|
env['PATH_INFO'] = uqrest
|
|
env['PATH_TRANSLATED'] = self.translate_path(uqrest)
|
|
env['SCRIPT_NAME'] = scriptname
|
|
if query:
|
|
env['QUERY_STRING'] = query
|
|
host = self.address_string()
|
|
if host != self.client_address[0]:
|
|
env['REMOTE_HOST'] = host
|
|
env['REMOTE_ADDR'] = self.client_address[0]
|
|
authorization = self.headers.getheader("authorization")
|
|
if authorization:
|
|
authorization = authorization.split()
|
|
if len(authorization) == 2:
|
|
import base64, binascii
|
|
env['AUTH_TYPE'] = authorization[0]
|
|
if authorization[0].lower() == "basic":
|
|
try:
|
|
authorization = base64.decodestring(authorization[1])
|
|
except binascii.Error:
|
|
pass
|
|
else:
|
|
authorization = authorization.split(':')
|
|
if len(authorization) == 2:
|
|
env['REMOTE_USER'] = authorization[0]
|
|
# XXX REMOTE_IDENT
|
|
if self.headers.typeheader is None:
|
|
env['CONTENT_TYPE'] = self.headers.type
|
|
else:
|
|
env['CONTENT_TYPE'] = self.headers.typeheader
|
|
length = self.headers.getheader('content-length')
|
|
if length:
|
|
env['CONTENT_LENGTH'] = length
|
|
accept = []
|
|
for line in self.headers.getallmatchingheaders('accept'):
|
|
if line[:1] in "\t\n\r ":
|
|
accept.append(line.strip())
|
|
else:
|
|
accept = accept + line[7:].split(',')
|
|
env['HTTP_ACCEPT'] = ','.join(accept)
|
|
ua = self.headers.getheader('user-agent')
|
|
if ua:
|
|
env['HTTP_USER_AGENT'] = ua
|
|
co = filter(None, self.headers.getheaders('cookie'))
|
|
if co:
|
|
env['HTTP_COOKIE'] = ', '.join(co)
|
|
# XXX Other HTTP_* headers
|
|
# 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, "")
|
|
os.environ.update(env)
|
|
|
|
self.send_response(200, "Script output follows")
|
|
|
|
decoded_query = query.replace('+', ' ')
|
|
|
|
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)
|
|
# throw away additional data [see bug #427345]
|
|
while select.select([self.rfile], [], [], 0)[0]:
|
|
if not self.rfile.read(1):
|
|
break
|
|
if sts:
|
|
self.log_error("CGI script exit status %#x", sts)
|
|
return
|
|
# Child
|
|
try:
|
|
try:
|
|
os.setuid(nobody)
|
|
except os.error:
|
|
pass
|
|
os.dup2(self.rfile.fileno(), 0)
|
|
os.dup2(self.wfile.fileno(), 1)
|
|
os.execve(scriptfile, args, os.environ)
|
|
except:
|
|
self.server.handle_error(self.request, self.client_address)
|
|
os._exit(127)
|
|
|
|
elif self.have_popen2 or self.have_popen3:
|
|
# Windows -- use popen2 or popen3 to create a subprocess
|
|
import shutil
|
|
if self.have_popen3:
|
|
popenx = os.popen3
|
|
else:
|
|
popenx = os.popen2
|
|
cmdline = scriptfile
|
|
if self.is_python(scriptfile):
|
|
interp = sys.executable
|
|
if interp.lower().endswith("w.exe"):
|
|
# On Windows, use python.exe, not pythonw.exe
|
|
interp = interp[:-5] + interp[-4:]
|
|
cmdline = "%s -u %s" % (interp, cmdline)
|
|
if '=' not in query and '"' not in query:
|
|
cmdline = '%s "%s"' % (cmdline, query)
|
|
self.log_message("command: %s", cmdline)
|
|
try:
|
|
nbytes = int(length)
|
|
except (TypeError, ValueError):
|
|
nbytes = 0
|
|
files = popenx(cmdline, 'b')
|
|
fi = files[0]
|
|
fo = files[1]
|
|
if self.have_popen3:
|
|
fe = files[2]
|
|
if self.command.lower() == "post" and nbytes > 0:
|
|
data = self.rfile.read(nbytes)
|
|
fi.write(data)
|
|
# throw away additional data [see bug #427345]
|
|
while select.select([self.rfile._sock], [], [], 0)[0]:
|
|
if not self.rfile._sock.recv(1):
|
|
break
|
|
fi.close()
|
|
shutil.copyfileobj(fo, self.wfile)
|
|
if self.have_popen3:
|
|
errors = fe.read()
|
|
fe.close()
|
|
if errors:
|
|
self.log_error('%s', errors)
|
|
sts = fo.close()
|
|
if sts:
|
|
self.log_error("CGI script exit status %#x", sts)
|
|
else:
|
|
self.log_message("CGI script exited OK")
|
|
|
|
else:
|
|
# Other O.S. -- execute script in this process
|
|
save_argv = sys.argv
|
|
save_stdin = sys.stdin
|
|
save_stdout = sys.stdout
|
|
save_stderr = sys.stderr
|
|
try:
|
|
save_cwd = os.getcwd()
|
|
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
|
|
os.chdir(save_cwd)
|
|
except SystemExit as sts:
|
|
self.log_error("CGI script exit status %s", str(sts))
|
|
else:
|
|
self.log_message("CGI script exited OK")
|
|
|
|
|
|
nobody = None
|
|
|
|
def nobody_uid():
|
|
"""Internal routine to get nobody's uid"""
|
|
global nobody
|
|
if nobody:
|
|
return nobody
|
|
try:
|
|
import pwd
|
|
except ImportError:
|
|
return -1
|
|
try:
|
|
nobody = pwd.getpwnam('nobody')[2]
|
|
except KeyError:
|
|
nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
|
|
return nobody
|
|
|
|
|
|
def executable(path):
|
|
"""Test for executable file."""
|
|
try:
|
|
st = os.stat(path)
|
|
except os.error:
|
|
return False
|
|
return st.st_mode & 0111 != 0
|
|
|
|
|
|
def test(HandlerClass = CGIHTTPRequestHandler,
|
|
ServerClass = BaseHTTPServer.HTTPServer):
|
|
SimpleHTTPServer.test(HandlerClass, ServerClass)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
test()
|