The Usual
diff --git a/Lib/dos-8x3/cgihttps.py b/Lib/dos-8x3/cgihttps.py
index 6a259a3..ba1e76b 100755
--- a/Lib/dos-8x3/cgihttps.py
+++ b/Lib/dos-8x3/cgihttps.py
@@ -3,28 +3,31 @@
 This module builds on SimpleHTTPServer by implementing GET and POST
 requests to cgi-bin scripts.
 
-If the os.fork() function is not present, this module will not work;
-SystemError will be raised instead.
+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.
+
+In all cases, the implementation is intentionally naive -- all
+requests are executed sychronously.
+
+SECURITY WARNING: DON'T USE THIS CODE UNLESS YOU ARE INSIDE A FIREWALL
+-- it may execute arbitrary Python code or external programs.
 
 """
 
 
-__version__ = "0.3"
+__version__ = "0.4"
 
 
 import os
+import sys
 import string
 import urllib
 import BaseHTTPServer
 import SimpleHTTPServer
 
 
-try:
-    os.fork
-except AttributeError:
-    raise SystemError, __name__ + " requires os.fork()"
-
-
 class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
 
     """Complete HTTP server with GET, HEAD and POST commands.
@@ -35,6 +38,10 @@
 
     """
 
+    # Determine platform specifics
+    have_fork = hasattr(os, 'fork')
+    have_popen2 = hasattr(os, 'popen2')
+
     # Make rfile unbuffered -- we need to read one line and then pass
     # the rest to a subprocess, so we can't use buffered input.
     rbufsize = 0
@@ -59,9 +66,9 @@
             return SimpleHTTPServer.SimpleHTTPRequestHandler.send_head(self)
 
     def is_cgi(self):
-        """test whether PATH corresponds to a CGI script.
+        """Test whether self.path corresponds to a CGI script.
 
-        Return a tuple (dir, rest) if PATH requires running a
+        Return a tuple (dir, rest) if self.path requires running a
         CGI script, None if not.  Note that rest begins with a
         slash if it is not empty.
 
@@ -83,6 +90,15 @@
 
     cgi_directories = ['/cgi-bin', '/htbin']
 
+    def is_executable(self, path):
+        """Test whether argument path is an executable file."""
+        return executable(path)
+
+    def is_python(self, path):
+        """Test whether argument path is a Python script."""
+        head, tail = os.path.splitext(path)
+        return tail.lower() in (".py", ".pyw")
+
     def run_cgi(self):
         """Execute a CGI script."""
         dir, rest = self.cgi_info
@@ -105,79 +121,152 @@
             self.send_error(403, "CGI script is not a plain file (%s)" %
                             `scriptname`)
             return
-        if not executable(scriptfile):
-            self.send_error(403, "CGI script is not executable (%s)" %
-                            `scriptname`)
-            return
-        nobody = nobody_uid()
-        self.send_response(200, "Script output follows")
-        self.wfile.flush() # Always flush before forking
-        pid = os.fork()
-        if pid != 0:
-            # Parent
-            pid, sts = os.waitpid(pid, 0)
-            if sts:
-                self.log_error("CGI script exit status x%x" % sts)
-            return
-        # Child
-        try:
-            # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
-            # XXX Much of the following could be prepared ahead of time!
-            env = {}
-            env['SERVER_SOFTWARE'] = self.version_string()
-            env['SERVER_NAME'] = self.server.server_name
-            env['GATEWAY_INTERFACE'] = 'CGI/1.1'
-            env['SERVER_PROTOCOL'] = self.protocol_version
-            env['SERVER_PORT'] = str(self.server.server_port)
-            env['REQUEST_METHOD'] = self.command
-            uqrest = urllib.unquote(rest)
-            env['PATH_INFO'] = uqrest
-            env['PATH_TRANSLATED'] = self.translate_path(uqrest)
-            env['SCRIPT_NAME'] = scriptname
-            if query:
-                env['QUERY_STRING'] = query
-            host = self.address_string()
-            if host != self.client_address[0]:
-                env['REMOTE_HOST'] = host
-            env['REMOTE_ADDR'] = self.client_address[0]
-            # AUTH_TYPE
-            # REMOTE_USER
-            # REMOTE_IDENT
-            if self.headers.typeheader is None:
-                env['CONTENT_TYPE'] = self.headers.type
+        ispy = self.is_python(scriptname)
+        if not ispy:
+            if not (self.have_fork or self.have_popen2):
+                self.send_error(403, "CGI script is not a Python script (%s)" %
+                                `scriptname`)
+                return
+            if not self.is_executable(scriptfile):
+                self.send_error(403, "CGI script is not executable (%s)" %
+                                `scriptname`)
+                return
+
+        # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html
+        # XXX Much of the following could be prepared ahead of time!
+        env = {}
+        env['SERVER_SOFTWARE'] = self.version_string()
+        env['SERVER_NAME'] = self.server.server_name
+        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
+        env['SERVER_PROTOCOL'] = self.protocol_version
+        env['SERVER_PORT'] = str(self.server.server_port)
+        env['REQUEST_METHOD'] = self.command
+        uqrest = urllib.unquote(rest)
+        env['PATH_INFO'] = uqrest
+        env['PATH_TRANSLATED'] = self.translate_path(uqrest)
+        env['SCRIPT_NAME'] = scriptname
+        if query:
+            env['QUERY_STRING'] = query
+        host = self.address_string()
+        if host != self.client_address[0]:
+            env['REMOTE_HOST'] = host
+        env['REMOTE_ADDR'] = self.client_address[0]
+        # XXX AUTH_TYPE
+        # XXX REMOTE_USER
+        # XXX REMOTE_IDENT
+        if self.headers.typeheader is None:
+            env['CONTENT_TYPE'] = self.headers.type
+        else:
+            env['CONTENT_TYPE'] = self.headers.typeheader
+        length = self.headers.getheader('content-length')
+        if length:
+            env['CONTENT_LENGTH'] = length
+        accept = []
+        for line in self.headers.getallmatchingheaders('accept'):
+            if line[:1] in string.whitespace:
+                accept.append(string.strip(line))
             else:
-                env['CONTENT_TYPE'] = self.headers.typeheader
-            length = self.headers.getheader('content-length')
-            if length:
-                env['CONTENT_LENGTH'] = length
-            accept = []
-            for line in self.headers.getallmatchingheaders('accept'):
-                if line[:1] in string.whitespace:
-                    accept.append(string.strip(line))
-                else:
-                    accept = accept + string.split(line[7:], ',')
-            env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
-            ua = self.headers.getheader('user-agent')
-            if ua:
-                env['HTTP_USER_AGENT'] = ua
-            co = filter(None, self.headers.getheaders('cookie'))
-            if co:
-                env['HTTP_COOKIE'] = string.join(co, ', ')
-            # XXX Other HTTP_* headers
-            decoded_query = string.replace(query, '+', ' ')
+                accept = accept + string.split(line[7:], ',')
+        env['HTTP_ACCEPT'] = string.joinfields(accept, ',')
+        ua = self.headers.getheader('user-agent')
+        if ua:
+            env['HTTP_USER_AGENT'] = ua
+        co = filter(None, self.headers.getheaders('cookie'))
+        if co:
+            env['HTTP_COOKIE'] = string.join(co, ', ')
+        # XXX Other HTTP_* headers
+        if not self.have_fork:
+            # Since we're setting the env in the parent, provide empty
+            # values to override previously set values
+            for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
+                      'HTTP_USER_AGENT', 'HTTP_COOKIE'):
+                env.setdefault(k, "")
+
+        self.send_response(200, "Script output follows")
+
+        decoded_query = string.replace(query, '+', ' ')
+
+        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)
+                if sts:
+                    self.log_error("CGI script exit status %#x", sts)
+                return
+            # Child
             try:
-                os.setuid(nobody)
-            except os.error:
-                pass
-            os.dup2(self.rfile.fileno(), 0)
-            os.dup2(self.wfile.fileno(), 1)
-            print scriptfile, script, decoded_query
-            os.execve(scriptfile,
-                      [script, decoded_query],
-                      env)
-        except:
-            self.server.handle_error(self.request, self.client_address)
-            os._exit(127)
+                try:
+                    os.setuid(nobody)
+                except os.error:
+                    pass
+                os.dup2(self.rfile.fileno(), 0)
+                os.dup2(self.wfile.fileno(), 1)
+                os.execve(scriptfile, args, env)
+            except:
+                self.server.handle_error(self.request, self.client_address)
+                os._exit(127)
+
+        elif self.have_popen2:
+            # Windows -- use popen2 to create a subprocess
+            import shutil
+            os.environ.update(env)
+            cmdline = scriptfile
+            if self.is_python(scriptfile):
+                interp = sys.executable
+                if interp.lower().endswith("w.exe"):
+                    # On Windows, use python.exe, not python.exe
+                    interp = interp[:-5] = interp[-4:]
+                cmdline = "%s %s" % (interp, cmdline)
+            if '=' not in query and '"' not in query:
+                cmdline = '%s "%s"' % (cmdline, query)
+            self.log_error("command: %s", cmdline)
+            try:
+                nbytes = int(length)
+            except:
+                nbytes = 0
+            fi, fo = os.popen2(cmdline)
+            if self.command.lower() == "post" and nbytes > 0:
+                data = self.rfile.read(nbytes)
+                fi.write(data)
+            fi.close()
+            shutil.copyfileobj(fo, self.wfile)
+            sts = fo.close()
+            if sts:
+                self.log_error("CGI script exit status %#x", sts)
+            else:
+                self.log_error("CGI script exited OK")
+
+        else:
+            # Other O.S. -- execute script in this process
+            os.environ.update(env)
+            save_argv = sys.argv
+            save_stdin = sys.stdin
+            save_stdout = sys.stdout
+            save_stderr = sys.stderr
+            try:
+                try:
+                    sys.argv = [scriptfile]
+                    if '=' not in decoded_query:
+                        sys.argv.append(decoded_query)
+                    sys.stdout = self.wfile
+                    sys.stdin = self.rfile
+                    execfile(scriptfile, {"__name__": "__main__"})
+                finally:
+                    sys.argv = save_argv
+                    sys.stdin = save_stdin
+                    sys.stdout = save_stdout
+                    sys.stderr = save_stderr
+            except SystemExit, sts:
+                self.log_error("CGI script exit status %s", str(sts))
+            else:
+                self.log_error("CGI script exited OK")
 
 
 nobody = None
@@ -187,7 +276,10 @@
     global nobody
     if nobody:
         return nobody
-    import pwd
+    try:
+        import pwd
+    except ImportError:
+        return -1
     try:
         nobody = pwd.getpwnam('nobody')[2]
     except KeyError: