home *** CD-ROM | disk | FTP | other *** search
/ PC Extra 07 & 08 / pca1507.iso / Software / psp8 / Data1.cab / CGIHTTPServer.py < prev    next >
Encoding:
Python Source  |  2003-04-22  |  10.3 KB  |  307 lines

  1. """CGI-savvy HTTP Server.
  2.  
  3. This module builds on SimpleHTTPServer by implementing GET and POST
  4. requests to cgi-bin scripts.
  5.  
  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.
  16.  
  17. """
  18.  
  19.  
  20. __version__ = "0.4"
  21.  
  22. __all__ = ["CGIHTTPRequestHandler"]
  23.  
  24. import os
  25. import sys
  26. import urllib
  27. import BaseHTTPServer
  28. import SimpleHTTPServer
  29.  
  30.  
  31. class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
  32.  
  33.     """Complete HTTP server with GET, HEAD and POST commands.
  34.  
  35.     GET and HEAD also support running CGI scripts.
  36.  
  37.     The POST command is *only* implemented for CGI scripts.
  38.  
  39.     """
  40.  
  41.     # Determine platform specifics
  42.     have_fork = hasattr(os, 'fork')
  43.     have_popen2 = hasattr(os, 'popen2')
  44.  
  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.
  47.     rbufsize = 0
  48.  
  49.     def do_POST(self):
  50.         """Serve a POST request.
  51.  
  52.         This is only implemented for CGI scripts.
  53.  
  54.         """
  55.  
  56.         if self.is_cgi():
  57.             self.run_cgi()
  58.         else:
  59.             self.send_error(501, "Can only POST to CGI scripts")
  60.  
  61.     def send_head(self):
  62.         """Version of send_head that support CGI scripts"""
  63.         if self.is_cgi():
  64.             return self.run_cgi()
  65.         else:
  66.             return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
  67.  
  68.     def is_cgi(self):
  69.         """Test whether self.path corresponds to a CGI script.
  70.  
  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.
  74.  
  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).
  79.  
  80.         """
  81.  
  82.         path = self.path
  83.  
  84.         for x in self.cgi_directories:
  85.             i = len(x)
  86.             if path[:i] == x and (not path[i:] or path[i] == '/'):
  87.                 self.cgi_info = path[:i], path[i+1:]
  88.                 return 1
  89.         return 0
  90.  
  91.     cgi_directories = ['/cgi-bin', '/htbin']
  92.  
  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.  
  102.     def run_cgi(self):
  103.         """Execute a CGI script."""
  104.         dir, rest = self.cgi_info
  105.         i = rest.rfind('?')
  106.         if i >= 0:
  107.             rest, query = rest[:i], rest[i+1:]
  108.         else:
  109.             query = ''
  110.         i = rest.find('/')
  111.         if i >= 0:
  112.             script, rest = rest[:i], rest[i:]
  113.         else:
  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`)
  119.             return
  120.         if not os.path.isfile(scriptfile):
  121.             self.send_error(403, "CGI script is not a plain file (%s)" %
  122.                             `scriptname`)
  123.             return
  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 "\t\n\r ":
  167.                 accept.append(line.strip())
  168.             else:
  169.                 accept = accept + line[7:].split(',')
  170.         env['HTTP_ACCEPT'] = ','.join(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'] = ', '.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.  
  185.         self.send_response(200, "Script output follows")
  186.  
  187.         decoded_query = query.replace('+', ' ')
  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.rfile.flush() # Always flush before forking
  196.             self.wfile.flush() # Always flush before forking
  197.             pid = os.fork()
  198.             if pid != 0:
  199.                 # Parent
  200.                 pid, sts = os.waitpid(pid, 0)
  201.                 if sts:
  202.                     self.log_error("CGI script exit status %#x", sts)
  203.                 return
  204.             # Child
  205.             try:
  206.                 try:
  207.                     os.setuid(nobody)
  208.                 except os.error:
  209.                     pass
  210.                 os.dup2(self.rfile.fileno(), 0)
  211.                 os.dup2(self.wfile.fileno(), 1)
  212.                 os.execve(scriptfile, args, env)
  213.             except:
  214.                 self.server.handle_error(self.request, self.client_address)
  215.                 os._exit(127)
  216.  
  217.         elif self.have_popen2:
  218.             # Windows -- use popen2 to create a subprocess
  219.             import shutil
  220.             os.environ.update(env)
  221.             cmdline = scriptfile
  222.             if self.is_python(scriptfile):
  223.                 interp = sys.executable
  224.                 if interp.lower().endswith("w.exe"):
  225.                     # On Windows, use python.exe, not pythonw.exe
  226.                     interp = interp[:-5] + interp[-4:]
  227.                 cmdline = "%s -u %s" % (interp, cmdline)
  228.             if '=' not in query and '"' not in query:
  229.                 cmdline = '%s "%s"' % (cmdline, query)
  230.             self.log_message("command: %s", cmdline)
  231.             try:
  232.                 nbytes = int(length)
  233.             except:
  234.                 nbytes = 0
  235.             fi, fo = os.popen2(cmdline, 'b')
  236.             if self.command.lower() == "post" and nbytes > 0:
  237.                 data = self.rfile.read(nbytes)
  238.                 fi.write(data)
  239.             fi.close()
  240.             shutil.copyfileobj(fo, self.wfile)
  241.             sts = fo.close()
  242.             if sts:
  243.                 self.log_error("CGI script exit status %#x", sts)
  244.             else:
  245.                 self.log_message("CGI script exited OK")
  246.  
  247.         else:
  248.             # Other O.S. -- execute script in this process
  249.             os.environ.update(env)
  250.             save_argv = sys.argv
  251.             save_stdin = sys.stdin
  252.             save_stdout = sys.stdout
  253.             save_stderr = sys.stderr
  254.             try:
  255.                 try:
  256.                     sys.argv = [scriptfile]
  257.                     if '=' not in decoded_query:
  258.                         sys.argv.append(decoded_query)
  259.                     sys.stdout = self.wfile
  260.                     sys.stdin = self.rfile
  261.                     execfile(scriptfile, {"__name__": "__main__"})
  262.                 finally:
  263.                     sys.argv = save_argv
  264.                     sys.stdin = save_stdin
  265.                     sys.stdout = save_stdout
  266.                     sys.stderr = save_stderr
  267.             except SystemExit, sts:
  268.                 self.log_error("CGI script exit status %s", str(sts))
  269.             else:
  270.                 self.log_message("CGI script exited OK")
  271.  
  272.  
  273. nobody = None
  274.  
  275. def nobody_uid():
  276.     """Internal routine to get nobody's uid"""
  277.     global nobody
  278.     if nobody:
  279.         return nobody
  280.     try:
  281.         import pwd
  282.     except ImportError:
  283.         return -1
  284.     try:
  285.         nobody = pwd.getpwnam('nobody')[2]
  286.     except KeyError:
  287.         nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
  288.     return nobody
  289.  
  290.  
  291. def executable(path):
  292.     """Test for executable file."""
  293.     try:
  294.         st = os.stat(path)
  295.     except os.error:
  296.         return 0
  297.     return st[0] & 0111 != 0
  298.  
  299.  
  300. def test(HandlerClass = CGIHTTPRequestHandler,
  301.          ServerClass = BaseHTTPServer.HTTPServer):
  302.     SimpleHTTPServer.test(HandlerClass, ServerClass)
  303.  
  304.  
  305. if __name__ == '__main__':
  306.     test()
  307.