PyXR

c:\python24\lib \ CGIHTTPServer.py



0001 """CGI-savvy HTTP Server.
0002 
0003 This module builds on SimpleHTTPServer by implementing GET and POST
0004 requests to cgi-bin scripts.
0005 
0006 If the os.fork() function is not present (e.g. on Windows),
0007 os.popen2() is used as a fallback, with slightly altered semantics; if
0008 that function is not present either (e.g. on Macintosh), only Python
0009 scripts are supported, and they are executed by the current process.
0010 
0011 In all cases, the implementation is intentionally naive -- all
0012 requests are executed sychronously.
0013 
0014 SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
0015 -- it may execute arbitrary Python code or external programs.
0016 
0017 """
0018 
0019 
0020 __version__ = "0.4"
0021 
0022 __all__ = ["CGIHTTPRequestHandler"]
0023 
0024 import os
0025 import sys
0026 import urllib
0027 import BaseHTTPServer
0028 import SimpleHTTPServer
0029 import select
0030 
0031 
0032 class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
0033 
0034     """Complete HTTP server with GET, HEAD and POST commands.
0035 
0036     GET and HEAD also support running CGI scripts.
0037 
0038     The POST command is *only* implemented for CGI scripts.
0039 
0040     """
0041 
0042     # Determine platform specifics
0043     have_fork = hasattr(os, 'fork')
0044     have_popen2 = hasattr(os, 'popen2')
0045     have_popen3 = hasattr(os, 'popen3')
0046 
0047     # Make rfile unbuffered -- we need to read one line and then pass
0048     # the rest to a subprocess, so we can't use buffered input.
0049     rbufsize = 0
0050 
0051     def do_POST(self):
0052         """Serve a POST request.
0053 
0054         This is only implemented for CGI scripts.
0055 
0056         """
0057 
0058         if self.is_cgi():
0059             self.run_cgi()
0060         else:
0061             self.send_error(501, "Can only POST to CGI scripts")
0062 
0063     def send_head(self):
0064         """Version of send_head that support CGI scripts"""
0065         if self.is_cgi():
0066             return self.run_cgi()
0067         else:
0068             return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
0069 
0070     def is_cgi(self):
0071         """Test whether self.path corresponds to a CGI script.
0072 
0073         Return a tuple (dir, rest) if self.path requires running a
0074         CGI script, None if not.  Note that rest begins with a
0075         slash if it is not empty.
0076 
0077         The default implementation tests whether the path
0078         begins with one of the strings in the list
0079         self.cgi_directories (and the next character is a '/'
0080         or the end of the string).
0081 
0082         """
0083 
0084         path = self.path
0085 
0086         for x in self.cgi_directories:
0087             i = len(x)
0088             if path[:i] == x and (not path[i:] or path[i] == '/'):
0089                 self.cgi_info = path[:i], path[i+1:]
0090                 return True
0091         return False
0092 
0093     cgi_directories = ['/cgi-bin', '/htbin']
0094 
0095     def is_executable(self, path):
0096         """Test whether argument path is an executable file."""
0097         return executable(path)
0098 
0099     def is_python(self, path):
0100         """Test whether argument path is a Python script."""
0101         head, tail = os.path.splitext(path)
0102         return tail.lower() in (".py", ".pyw")
0103 
0104     def run_cgi(self):
0105         """Execute a CGI script."""
0106         dir, rest = self.cgi_info
0107         i = rest.rfind('?')
0108         if i >= 0:
0109             rest, query = rest[:i], rest[i+1:]
0110         else:
0111             query = ''
0112         i = rest.find('/')
0113         if i >= 0:
0114             script, rest = rest[:i], rest[i:]
0115         else:
0116             script, rest = rest, ''
0117         scriptname = dir + '/' + script
0118         scriptfile = self.translate_path(scriptname)
0119         if not os.path.exists(scriptfile):
0120             self.send_error(404, "No such CGI script (%r)" % scriptname)
0121             return
0122         if not os.path.isfile(scriptfile):
0123             self.send_error(403, "CGI script is not a plain file (%r)" %
0124                             scriptname)
0125             return
0126         ispy = self.is_python(scriptname)
0127         if not ispy:
0128             if not (self.have_fork or self.have_popen2 or self.have_popen3):
0129                 self.send_error(403, "CGI script is not a Python script (%r)" %
0130                                 scriptname)
0131                 return
0132             if not self.is_executable(scriptfile):
0133                 self.send_error(403, "CGI script is not executable (%r)" %
0134                                 scriptname)
0135                 return
0136 
0137         # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
0138         # XXX Much of the following could be prepared ahead of time!
0139         env = {}
0140         env['SERVER_SOFTWARE'] = self.version_string()
0141         env['SERVER_NAME'] = self.server.server_name
0142         env['GATEWAY_INTERFACE'] = 'CGI/1.1'
0143         env['SERVER_PROTOCOL'] = self.protocol_version
0144         env['SERVER_PORT'] = str(self.server.server_port)
0145         env['REQUEST_METHOD'] = self.command
0146         uqrest = urllib.unquote(rest)
0147         env['PATH_INFO'] = uqrest
0148         env['PATH_TRANSLATED'] = self.translate_path(uqrest)
0149         env['SCRIPT_NAME'] = scriptname
0150         if query:
0151             env['QUERY_STRING'] = query
0152         host = self.address_string()
0153         if host != self.client_address[0]:
0154             env['REMOTE_HOST'] = host
0155         env['REMOTE_ADDR'] = self.client_address[0]
0156         authorization = self.headers.getheader("authorization")
0157         if authorization:
0158             authorization = authorization.split()
0159             if len(authorization) == 2:
0160                 import base64, binascii
0161                 env['AUTH_TYPE'] = authorization[0]
0162                 if authorization[0].lower() == "basic":
0163                     try:
0164                         authorization = base64.decodestring(authorization[1])
0165                     except binascii.Error:
0166                         pass
0167                     else:
0168                         authorization = authorization.split(':')
0169                         if len(authorization) == 2:
0170                             env['REMOTE_USER'] = authorization[0]
0171         # XXX REMOTE_IDENT
0172         if self.headers.typeheader is None:
0173             env['CONTENT_TYPE'] = self.headers.type
0174         else:
0175             env['CONTENT_TYPE'] = self.headers.typeheader
0176         length = self.headers.getheader('content-length')
0177         if length:
0178             env['CONTENT_LENGTH'] = length
0179         accept = []
0180         for line in self.headers.getallmatchingheaders('accept'):
0181             if line[:1] in "\t\n\r ":
0182                 accept.append(line.strip())
0183             else:
0184                 accept = accept + line[7:].split(',')
0185         env['HTTP_ACCEPT'] = ','.join(accept)
0186         ua = self.headers.getheader('user-agent')
0187         if ua:
0188             env['HTTP_USER_AGENT'] = ua
0189         co = filter(None, self.headers.getheaders('cookie'))
0190         if co:
0191             env['HTTP_COOKIE'] = ', '.join(co)
0192         # XXX Other HTTP_* headers
0193         # Since we're setting the env in the parent, provide empty
0194         # values to override previously set values
0195         for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
0196                   'HTTP_USER_AGENT', 'HTTP_COOKIE'):
0197             env.setdefault(k, "")
0198         os.environ.update(env)
0199 
0200         self.send_response(200, "Script output follows")
0201 
0202         decoded_query = query.replace('+', ' ')
0203 
0204         if self.have_fork:
0205             # Unix -- fork as we should
0206             args = [script]
0207             if '=' not in decoded_query:
0208                 args.append(decoded_query)
0209             nobody = nobody_uid()
0210             self.wfile.flush() # Always flush before forking
0211             pid = os.fork()
0212             if pid != 0:
0213                 # Parent
0214                 pid, sts = os.waitpid(pid, 0)
0215                 # throw away additional data [see bug #427345]
0216                 while select.select([self.rfile], [], [], 0)[0]:
0217                     if not self.rfile.read(1):
0218                         break
0219                 if sts:
0220                     self.log_error("CGI script exit status %#x", sts)
0221                 return
0222             # Child
0223             try:
0224                 try:
0225                     os.setuid(nobody)
0226                 except os.error:
0227                     pass
0228                 os.dup2(self.rfile.fileno(), 0)
0229                 os.dup2(self.wfile.fileno(), 1)
0230                 os.execve(scriptfile, args, os.environ)
0231             except:
0232                 self.server.handle_error(self.request, self.client_address)
0233                 os._exit(127)
0234 
0235         elif self.have_popen2 or self.have_popen3:
0236             # Windows -- use popen2 or popen3 to create a subprocess
0237             import shutil
0238             if self.have_popen3:
0239                 popenx = os.popen3
0240             else:
0241                 popenx = os.popen2
0242             cmdline = scriptfile
0243             if self.is_python(scriptfile):
0244                 interp = sys.executable
0245                 if interp.lower().endswith("w.exe"):
0246                     # On Windows, use python.exe, not pythonw.exe
0247                     interp = interp[:-5] + interp[-4:]
0248                 cmdline = "%s -u %s" % (interp, cmdline)
0249             if '=' not in query and '"' not in query:
0250                 cmdline = '%s "%s"' % (cmdline, query)
0251             self.log_message("command: %s", cmdline)
0252             try:
0253                 nbytes = int(length)
0254             except (TypeError, ValueError):
0255                 nbytes = 0
0256             files = popenx(cmdline, 'b')
0257             fi = files[0]
0258             fo = files[1]
0259             if self.have_popen3:
0260                 fe = files[2]
0261             if self.command.lower() == "post" and nbytes > 0:
0262                 data = self.rfile.read(nbytes)
0263                 fi.write(data)
0264             # throw away additional data [see bug #427345]
0265             while select.select([self.rfile._sock], [], [], 0)[0]:
0266                 if not self.rfile._sock.recv(1):
0267                     break
0268             fi.close()
0269             shutil.copyfileobj(fo, self.wfile)
0270             if self.have_popen3:
0271                 errors = fe.read()
0272                 fe.close()
0273                 if errors:
0274                     self.log_error('%s', errors)
0275             sts = fo.close()
0276             if sts:
0277                 self.log_error("CGI script exit status %#x", sts)
0278             else:
0279                 self.log_message("CGI script exited OK")
0280 
0281         else:
0282             # Other O.S. -- execute script in this process
0283             save_argv = sys.argv
0284             save_stdin = sys.stdin
0285             save_stdout = sys.stdout
0286             save_stderr = sys.stderr
0287             try:
0288                 save_cwd = os.getcwd()
0289                 try:
0290                     sys.argv = [scriptfile]
0291                     if '=' not in decoded_query:
0292                         sys.argv.append(decoded_query)
0293                     sys.stdout = self.wfile
0294                     sys.stdin = self.rfile
0295                     execfile(scriptfile, {"__name__": "__main__"})
0296                 finally:
0297                     sys.argv = save_argv
0298                     sys.stdin = save_stdin
0299                     sys.stdout = save_stdout
0300                     sys.stderr = save_stderr
0301                     os.chdir(save_cwd)
0302             except SystemExit, sts:
0303                 self.log_error("CGI script exit status %s", str(sts))
0304             else:
0305                 self.log_message("CGI script exited OK")
0306 
0307 
0308 nobody = None
0309 
0310 def nobody_uid():
0311     """Internal routine to get nobody's uid"""
0312     global nobody
0313     if nobody:
0314         return nobody
0315     try:
0316         import pwd
0317     except ImportError:
0318         return -1
0319     try:
0320         nobody = pwd.getpwnam('nobody')[2]
0321     except KeyError:
0322         nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
0323     return nobody
0324 
0325 
0326 def executable(path):
0327     """Test for executable file."""
0328     try:
0329         st = os.stat(path)
0330     except os.error:
0331         return False
0332     return st.st_mode & 0111 != 0
0333 
0334 
0335 def test(HandlerClass = CGIHTTPRequestHandler,
0336          ServerClass = BaseHTTPServer.HTTPServer):
0337     SimpleHTTPServer.test(HandlerClass, ServerClass)
0338 
0339 
0340 if __name__ == '__main__':
0341     test()
0342 

Generated by PyXR 0.9.4
SourceForge.net Logo