home *** CD-ROM | disk | FTP | other *** search
/ Freelog Special Freeware 31 / FreelogHS31.iso / Texte / scribus / scribus-1.3.3.9-win32-install.exe / lib / CGIHTTPServer.py < prev    next >
Text File  |  2004-08-30  |  12KB  |  342 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. import select
  30.  
  31.  
  32. class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
  33.  
  34.     """Complete HTTP server with GET, HEAD and POST commands.
  35.  
  36.     GET and HEAD also support running CGI scripts.
  37.  
  38.     The POST command is *only* implemented for CGI scripts.
  39.  
  40.     """
  41.  
  42.     # Determine platform specifics
  43.     have_fork = hasattr(os, 'fork')
  44.     have_popen2 = hasattr(os, 'popen2')
  45.     have_popen3 = hasattr(os, 'popen3')
  46.  
  47.     # Make rfile unbuffered -- we need to read one line and then pass
  48.     # the rest to a subprocess, so we can't use buffered input.
  49.     rbufsize = 0
  50.  
  51.     def do_POST(self):
  52.         """Serve a POST request.
  53.  
  54.         This is only implemented for CGI scripts.
  55.  
  56.         """
  57.  
  58.         if self.is_cgi():
  59.             self.run_cgi()
  60.         else:
  61.             self.send_error(501, "Can only POST to CGI scripts")
  62.  
  63.     def send_head(self):
  64.         """Version of send_head that support CGI scripts"""
  65.         if self.is_cgi():
  66.             return self.run_cgi()
  67.         else:
  68.             return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
  69.  
  70.     def is_cgi(self):
  71.         """Test whether self.path corresponds to a CGI script.
  72.  
  73.         Return a tuple (dir, rest) if self.path requires running a
  74.         CGI script, None if not.  Note that rest begins with a
  75.         slash if it is not empty.
  76.  
  77.         The default implementation tests whether the path
  78.         begins with one of the strings in the list
  79.         self.cgi_directories (and the next character is a '/'
  80.         or the end of the string).
  81.  
  82.         """
  83.  
  84.         path = self.path
  85.  
  86.         for x in self.cgi_directories:
  87.             i = len(x)
  88.             if path[:i] == x and (not path[i:] or path[i] == '/'):
  89.                 self.cgi_info = path[:i], path[i+1:]
  90.                 return True
  91.         return False
  92.  
  93.     cgi_directories = ['/cgi-bin', '/htbin']
  94.  
  95.     def is_executable(self, path):
  96.         """Test whether argument path is an executable file."""
  97.         return executable(path)
  98.  
  99.     def is_python(self, path):
  100.         """Test whether argument path is a Python script."""
  101.         head, tail = os.path.splitext(path)
  102.         return tail.lower() in (".py", ".pyw")
  103.  
  104.     def run_cgi(self):
  105.         """Execute a CGI script."""
  106.         dir, rest = self.cgi_info
  107.         i = rest.rfind('?')
  108.         if i >= 0:
  109.             rest, query = rest[:i], rest[i+1:]
  110.         else:
  111.             query = ''
  112.         i = rest.find('/')
  113.         if i >= 0:
  114.             script, rest = rest[:i], rest[i:]
  115.         else:
  116.             script, rest = rest, ''
  117.         scriptname = dir + '/' + script
  118.         scriptfile = self.translate_path(scriptname)
  119.         if not os.path.exists(scriptfile):
  120.             self.send_error(404, "No such CGI script (%r)" % scriptname)
  121.             return
  122.         if not os.path.isfile(scriptfile):
  123.             self.send_error(403, "CGI script is not a plain file (%r)" %
  124.                             scriptname)
  125.             return
  126.         ispy = self.is_python(scriptname)
  127.         if not ispy:
  128.             if not (self.have_fork or self.have_popen2 or self.have_popen3):
  129.                 self.send_error(403, "CGI script is not a Python script (%r)" %
  130.                                 scriptname)
  131.                 return
  132.             if not self.is_executable(scriptfile):
  133.                 self.send_error(403, "CGI script is not executable (%r)" %
  134.                                 scriptname)
  135.                 return
  136.  
  137.         # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
  138.         # XXX Much of the following could be prepared ahead of time!
  139.         env = {}
  140.         env['SERVER_SOFTWARE'] = self.version_string()
  141.         env['SERVER_NAME'] = self.server.server_name
  142.         env['GATEWAY_INTERFACE'] = 'CGI/1.1'
  143.         env['SERVER_PROTOCOL'] = self.protocol_version
  144.         env['SERVER_PORT'] = str(self.server.server_port)
  145.         env['REQUEST_METHOD'] = self.command
  146.         uqrest = urllib.unquote(rest)
  147.         env['PATH_INFO'] = uqrest
  148.         env['PATH_TRANSLATED'] = self.translate_path(uqrest)
  149.         env['SCRIPT_NAME'] = scriptname
  150.         if query:
  151.             env['QUERY_STRING'] = query
  152.         host = self.address_string()
  153.         if host != self.client_address[0]:
  154.             env['REMOTE_HOST'] = host
  155.         env['REMOTE_ADDR'] = self.client_address[0]
  156.         authorization = self.headers.getheader("authorization")
  157.         if authorization:
  158.             authorization = authorization.split()
  159.             if len(authorization) == 2:
  160.                 import base64, binascii
  161.                 env['AUTH_TYPE'] = authorization[0]
  162.                 if authorization[0].lower() == "basic":
  163.                     try:
  164.                         authorization = base64.decodestring(authorization[1])
  165.                     except binascii.Error:
  166.                         pass
  167.                     else:
  168.                         authorization = authorization.split(':')
  169.                         if len(authorization) == 2:
  170.                             env['REMOTE_USER'] = authorization[0]
  171.         # XXX REMOTE_IDENT
  172.         if self.headers.typeheader is None:
  173.             env['CONTENT_TYPE'] = self.headers.type
  174.         else:
  175.             env['CONTENT_TYPE'] = self.headers.typeheader
  176.         length = self.headers.getheader('content-length')
  177.         if length:
  178.             env['CONTENT_LENGTH'] = length
  179.         accept = []
  180.         for line in self.headers.getallmatchingheaders('accept'):
  181.             if line[:1] in "\t\n\r ":
  182.                 accept.append(line.strip())
  183.             else:
  184.                 accept = accept + line[7:].split(',')
  185.         env['HTTP_ACCEPT'] = ','.join(accept)
  186.         ua = self.headers.getheader('user-agent')
  187.         if ua:
  188.             env['HTTP_USER_AGENT'] = ua
  189.         co = filter(None, self.headers.getheaders('cookie'))
  190.         if co:
  191.             env['HTTP_COOKIE'] = ', '.join(co)
  192.         # XXX Other HTTP_* headers
  193.         # Since we're setting the env in the parent, provide empty
  194.         # values to override previously set values
  195.         for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
  196.                   'HTTP_USER_AGENT', 'HTTP_COOKIE'):
  197.             env.setdefault(k, "")
  198.         os.environ.update(env)
  199.  
  200.         self.send_response(200, "Script output follows")
  201.  
  202.         decoded_query = query.replace('+', ' ')
  203.  
  204.         if self.have_fork:
  205.             # Unix -- fork as we should
  206.             args = [script]
  207.             if '=' not in decoded_query:
  208.                 args.append(decoded_query)
  209.             nobody = nobody_uid()
  210.             self.wfile.flush() # Always flush before forking
  211.             pid = os.fork()
  212.             if pid != 0:
  213.                 # Parent
  214.                 pid, sts = os.waitpid(pid, 0)
  215.                 # throw away additional data [see bug #427345]
  216.                 while select.select([self.rfile], [], [], 0)[0]:
  217.                     if not self.rfile.read(1):
  218.                         break
  219.                 if sts:
  220.                     self.log_error("CGI script exit status %#x", sts)
  221.                 return
  222.             # Child
  223.             try:
  224.                 try:
  225.                     os.setuid(nobody)
  226.                 except os.error:
  227.                     pass
  228.                 os.dup2(self.rfile.fileno(), 0)
  229.                 os.dup2(self.wfile.fileno(), 1)
  230.                 os.execve(scriptfile, args, os.environ)
  231.             except:
  232.                 self.server.handle_error(self.request, self.client_address)
  233.                 os._exit(127)
  234.  
  235.         elif self.have_popen2 or self.have_popen3:
  236.             # Windows -- use popen2 or popen3 to create a subprocess
  237.             import shutil
  238.             if self.have_popen3:
  239.                 popenx = os.popen3
  240.             else:
  241.                 popenx = os.popen2
  242.             cmdline = scriptfile
  243.             if self.is_python(scriptfile):
  244.                 interp = sys.executable
  245.                 if interp.lower().endswith("w.exe"):
  246.                     # On Windows, use python.exe, not pythonw.exe
  247.                     interp = interp[:-5] + interp[-4:]
  248.                 cmdline = "%s -u %s" % (interp, cmdline)
  249.             if '=' not in query and '"' not in query:
  250.                 cmdline = '%s "%s"' % (cmdline, query)
  251.             self.log_message("command: %s", cmdline)
  252.             try:
  253.                 nbytes = int(length)
  254.             except (TypeError, ValueError):
  255.                 nbytes = 0
  256.             files = popenx(cmdline, 'b')
  257.             fi = files[0]
  258.             fo = files[1]
  259.             if self.have_popen3:
  260.                 fe = files[2]
  261.             if self.command.lower() == "post" and nbytes > 0:
  262.                 data = self.rfile.read(nbytes)
  263.                 fi.write(data)
  264.             # throw away additional data [see bug #427345]
  265.             while select.select([self.rfile._sock], [], [], 0)[0]:
  266.                 if not self.rfile._sock.recv(1):
  267.                     break
  268.             fi.close()
  269.             shutil.copyfileobj(fo, self.wfile)
  270.             if self.have_popen3:
  271.                 errors = fe.read()
  272.                 fe.close()
  273.                 if errors:
  274.                     self.log_error('%s', errors)
  275.             sts = fo.close()
  276.             if sts:
  277.                 self.log_error("CGI script exit status %#x", sts)
  278.             else:
  279.                 self.log_message("CGI script exited OK")
  280.  
  281.         else:
  282.             # Other O.S. -- execute script in this process
  283.             save_argv = sys.argv
  284.             save_stdin = sys.stdin
  285.             save_stdout = sys.stdout
  286.             save_stderr = sys.stderr
  287.             try:
  288.                 save_cwd = os.getcwd()
  289.                 try:
  290.                     sys.argv = [scriptfile]
  291.                     if '=' not in decoded_query:
  292.                         sys.argv.append(decoded_query)
  293.                     sys.stdout = self.wfile
  294.                     sys.stdin = self.rfile
  295.                     execfile(scriptfile, {"__name__": "__main__"})
  296.                 finally:
  297.                     sys.argv = save_argv
  298.                     sys.stdin = save_stdin
  299.                     sys.stdout = save_stdout
  300.                     sys.stderr = save_stderr
  301.                     os.chdir(save_cwd)
  302.             except SystemExit, sts:
  303.                 self.log_error("CGI script exit status %s", str(sts))
  304.             else:
  305.                 self.log_message("CGI script exited OK")
  306.  
  307.  
  308. nobody = None
  309.  
  310. def nobody_uid():
  311.     """Internal routine to get nobody's uid"""
  312.     global nobody
  313.     if nobody:
  314.         return nobody
  315.     try:
  316.         import pwd
  317.     except ImportError:
  318.         return -1
  319.     try:
  320.         nobody = pwd.getpwnam('nobody')[2]
  321.     except KeyError:
  322.         nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
  323.     return nobody
  324.  
  325.  
  326. def executable(path):
  327.     """Test for executable file."""
  328.     try:
  329.         st = os.stat(path)
  330.     except os.error:
  331.         return False
  332.     return st.st_mode & 0111 != 0
  333.  
  334.  
  335. def test(HandlerClass = CGIHTTPRequestHandler,
  336.          ServerClass = BaseHTTPServer.HTTPServer):
  337.     SimpleHTTPServer.test(HandlerClass, ServerClass)
  338.  
  339.  
  340. if __name__ == '__main__':
  341.     test()
  342.