diff options
| author | Guido van Rossum <guido@python.org> | 2000-09-19 04:01:01 +0000 | 
|---|---|---|
| committer | Guido van Rossum <guido@python.org> | 2000-09-19 04:01:01 +0000 | 
| commit | e7d6b0a22e07adfa33cfa8658acf4c0b3cbecb39 (patch) | |
| tree | 303b93185e8c5f1ddfc5403f135b9a647c72d1f0 /Lib/CGIHTTPServer.py | |
| parent | d9a8e965433e03f598089153f3c51ac6d6fb295f (diff) | |
| download | cpython-git-e7d6b0a22e07adfa33cfa8658acf4c0b3cbecb39.tar.gz | |
An honest attempt to make this work on Unix, Windows, and even
Macintosh (the latter untested).
This closes Bug #110839.
Diffstat (limited to 'Lib/CGIHTTPServer.py')
| -rw-r--r-- | Lib/CGIHTTPServer.py | 254 | 
1 files changed, 173 insertions, 81 deletions
diff --git a/Lib/CGIHTTPServer.py b/Lib/CGIHTTPServer.py index 6a259a32da..ba1e76bb3e 100644 --- a/Lib/CGIHTTPServer.py +++ b/Lib/CGIHTTPServer.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 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):      """ +    # 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 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):              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 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):      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 @@ class CGIHTTPRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):              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() +        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: +                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") -        self.wfile.flush() # Always flush before forking -        pid = os.fork() -        if pid != 0: -            # Parent -            pid, sts = os.waitpid(pid, 0) + +        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: +                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%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 +                self.log_error("CGI script exit status %#x", sts)              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, '+', ' ') +                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: -                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: +                    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 @@ def nobody_uid():      global nobody      if nobody:          return nobody -    import pwd +    try: +        import pwd +    except ImportError: +        return -1      try:          nobody = pwd.getpwnam('nobody')[2]      except KeyError:  | 
