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