--- CGIHTTPServer.py-r54234 2007-03-14 18:00:28.000000000 +0100 +++ CGIHTTPServer.py 2007-03-14 20:38:04.000000000 +0100 @@ -3,10 +3,11 @@ 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. +It uses the subprocess module for executing the scripts, if either the +os.fork() function is present or running on Windows. On Windows +semantics are slightly altered. In otehr cases (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. @@ -29,6 +30,7 @@ import BaseHTTPServer import SimpleHTTPServer import select +import subprocess class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): @@ -43,8 +45,6 @@ # 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. @@ -103,6 +103,13 @@ head, tail = os.path.splitext(path) return tail.lower() in (".py", ".pyw") + def __setuid_nobody(self): + nobody = nobody_uid() + try: + os.setuid(nobody) + except os.error: + pass + def run_cgi(self): """Execute a CGI script.""" path = self.path @@ -146,7 +153,7 @@ return ispy = self.is_python(scriptname) if not ispy: - if not (self.have_fork or self.have_popen2 or self.have_popen3): + if not (self.have_fork or subprocess.mswindows): self.send_error(403, "CGI script is not a Python script (%r)" % scriptname) return @@ -225,78 +232,74 @@ 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) + class length_wrapped_file: + # a simple wrapper to terminate file length + def __init__(self, fp, len): + self.fp = fp + self.length = len + self.remaining = len - 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) + def read(self, length): + length = min(length, self.remaining) + buf = '' + if length > 0: + buf = self.fp.read(length) + self.remaining -= len(buf) + return buf + + nbytes = 0 + if self.command.lower() == "post": 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] + pass + rfile = length_wrapped_file(self.rfile, nbytes) + + if subprocess.mswindows or self.have_fork: + import shutil + if subprocess.mswindows: + # Windows + preexec_fn = None + args = [scriptfile] + if ispy: + interp = sys.executable + if interp.lower().endswith("w.exe"): + # On Windows, use python.exe, not pythonw.exe + interp = interp[:-5] + interp[-4:] + args = [interp, '-u', args] + + # semantic differs: unix uses decoded_query! + if '=' not in query and '"' not in query: + args.append(query) + else: + # Unix + preexec_fn = self.__setuid_nobody + args = [scriptfile] + if '=' not in decoded_query: + args.append(decoded_query) + + self.log_message("command: %s", args) + self.wfile.flush() # Always flush before forking + sub = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=self.wfile, + stderr=subprocess.PIPE, + preexec_fn=preexec_fn, + env=os.environ) + # copy input data to subprocess + if nbytes: + shutil.copyfileobj(rfile, sub.stdin) + sub.stdin.close() + # throw away additional input 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() + sts = sub.wait() + + errors = sub.stderr.read().strip() + sub.stderr.close() + if errors: + self.log_error('%s', errors) if sts: self.log_error("CGI script exit status %#x", sts) else: @@ -315,7 +318,7 @@ if '=' not in decoded_query: sys.argv.append(decoded_query) sys.stdout = self.wfile - sys.stdin = self.rfile + sys.stdin = rfile or self.rfile execfile(scriptfile, {"__name__": "__main__"}) finally: sys.argv = save_argv