Patch #473586: Implement CGIXMLRPCRequestHandler.
diff --git a/Lib/SimpleXMLRPCServer.py b/Lib/SimpleXMLRPCServer.py
index 0a91683..6320184 100644
--- a/Lib/SimpleXMLRPCServer.py
+++ b/Lib/SimpleXMLRPCServer.py
@@ -2,9 +2,12 @@
 
 This module can be used to create simple XML-RPC servers
 by creating a server and either installing functions, a
-class instance, or by extending the SimpleXMLRPCRequestHandler
+class instance, or by extending the SimpleXMLRPCServer
 class.
 
+It can also be used to handle XML-RPC requests in a CGI
+environment using CGIXMLRPCRequestHandler.
+
 A list of possible usage patterns follows:
 
 1. Install functions:
@@ -22,29 +25,53 @@
         # string.func_name
         import string
         self.string = string
+    def _listMethods(self):
+        # implement this method so that system.listMethods
+        # knows to advertise the strings methods
+        return list_public_methods(self) + \
+                ['string.' + method for method in list_public_methods(self.string)]
     def pow(self, x, y): return pow(x, y)
     def add(self, x, y) : return x + y
+    
 server = SimpleXMLRPCServer(("localhost", 8000))
+server.register_introspection_functions()
 server.register_instance(MyFuncs())
 server.serve_forever()
 
 3. Install an instance with custom dispatch method:
 
 class Math:
+    def _listMethods(self):
+        # this method must be present for system.listMethods
+        # to work
+        return ['add', 'pow']
+    def _methodHelp(self, method):
+        # this method must be present for system.methodHelp
+        # to work
+        if method == 'add':
+            return "add(2,3) => 5"
+        elif method == 'pow':
+            return "pow(x, y[, z]) => number"
+        else:
+            # By convention, return empty
+            # string if no help is available
+            return ""
     def _dispatch(self, method, params):
         if method == 'pow':
-            return apply(pow, params)
+            return pow(*params)
         elif method == 'add':
             return params[0] + params[1]
         else:
             raise 'bad method'
+
 server = SimpleXMLRPCServer(("localhost", 8000))
+server.register_introspection_functions()
 server.register_instance(Math())
 server.serve_forever()
 
-4. Subclass SimpleXMLRPCRequestHandler:
+4. Subclass SimpleXMLRPCServer:
 
-class MathHandler(SimpleXMLRPCRequestHandler):
+class MathServer(SimpleXMLRPCServer):
     def _dispatch(self, method, params):
         try:
             # We are forcing the 'export_' prefix on methods that are
@@ -54,78 +81,263 @@
         except AttributeError:
             raise Exception('method "%s" is not supported' % method)
         else:
-            return apply(func, params)
-
-    def log_message(self, format, *args):
-        pass # maybe do something fancy like write the messages to a file
+            return func(*params)
 
     def export_add(self, x, y):
         return x + y
 
-server = SimpleXMLRPCServer(("localhost", 8000), MathHandler)
+server = MathServer(("localhost", 8000))
 server.serve_forever()
+
+5. CGI script:
+
+server = CGIXMLRPCRequestHandler()
+server.register_function(pow)
+server.handle_request()
 """
 
 # Written by Brian Quinlan (brian@sweetapp.com).
 # Based on code written by Fredrik Lundh.
 
 import xmlrpclib
+from xmlrpclib import Fault
 import SocketServer
 import BaseHTTPServer
 import sys
+import types
+import os
 
-class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
-    """Simple XML-RPC request handler class.
+def resolve_dotted_attribute(obj, attr):
+    """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
 
-    Handles all HTTP POST requests and attempts to decode them as
-    XML-RPC requests.
-
-    XML-RPC requests are dispatched to the _dispatch method, which
-    may be overriden by subclasses. The default implementation attempts
-    to dispatch XML-RPC calls to the functions or instance installed
-    in the server.
+    Resolves a dotted attribute name to an object.  Raises
+    an AttributeError if any attribute in the chain starts with a '_'.
     """
 
-    def do_POST(self):
-        """Handles the HTTP POST request.
+    for i in attr.split('.'):
+        if i.startswith('_'):
+            raise AttributeError(
+                'attempt to access private attribute "%s"' % i
+                )
+        else:
+            obj = getattr(obj,i)
+    return obj
 
-        Attempts to interpret all HTTP POST requests as XML-RPC calls,
-        which are forwarded to the _dispatch method for handling.
+def list_public_methods(obj):
+    """Returns a list of attribute strings, found in the specified
+    object, which represent callable attributes"""
+
+    return [member for member in dir(obj)
+                if not member.startswith('_') and
+                    callable(getattr(obj, member))]
+
+def remove_duplicates(lst):
+    """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
+
+    Returns a copy of a list without duplicates. Every list
+    item must be hashable and the order of the items in the
+    resulting list is not defined.
+    """    
+    u = {}
+    for x in lst:
+        u[x] = 1
+
+    return u.keys()
+
+class SimpleXMLRPCDispatcher:
+    """Mix-in class that dispatches XML-RPC requests.
+
+    This class is used to register XML-RPC method handlers
+    and then to dispatch them. There should never be any
+    reason to instantiate this class directly.
+    """
+    
+    def __init__(self):
+        self.funcs = {}
+        self.instance = None
+
+    def register_instance(self, instance):
+        """Registers an instance to respond to XML-RPC requests.
+
+        Only one instance can be installed at a time.
+
+        If the registered instance has a _dispatch method then that
+        method will be called with the name of the XML-RPC method and
+        it's parameters as a tuple
+        e.g. instance._dispatch('add',(2,3))
+
+        If the registered instance does not have a _dispatch method
+        then the instance will be searched to find a matching method
+        and, if found, will be called. Methods beginning with an '_'
+        are considered private and will not be called by
+        SimpleXMLRPCServer.
+
+        If a registered function matches a XML-RPC request, then it
+        will be called instead of the registered instance.
         """
 
+        self.instance = instance
+
+    def register_function(self, function, name = None):
+        """Registers a function to respond to XML-RPC requests.
+
+        The optional name argument can be used to set a Unicode name
+        for the function.
+        """
+
+        if name is None:
+            name = function.__name__
+        self.funcs[name] = function
+
+    def register_introspection_functions(self):
+        """Registers the XML-RPC introspection methods in the system
+        namespace.
+
+        see http://xmlrpc.usefulinc.com/doc/reserved.html
+        """
+        
+        self.funcs.update({'system.listMethods' : self.system_listMethods,
+                      'system.methodSignature' : self.system_methodSignature,
+                      'system.methodHelp' : self.system_methodHelp})
+
+    def register_multicall_functions(self):
+        """Registers the XML-RPC multicall method in the system
+        namespace.
+
+        see http://www.xmlrpc.com/discuss/msgReader$1208"""
+        
+        self.funcs.update({'system.multicall' : self.system_multicall})
+        
+    def _marshaled_dispatch(self, data, dispatch_method = None):
+        """Dispatches an XML-RPC method from marshalled (XML) data.
+        
+        XML-RPC methods are dispatched from the marshalled (XML) data
+        using the _dispatch method and the result is returned as
+        marshalled data. For backwards compatibility, a dispatch
+        function can be provided as an argument (see comment in 
+        SimpleXMLRPCRequestHandler.do_POST) but overriding the
+        existing method through subclassing is the prefered means
+        of changing method dispatch behavior.
+        """
+        
+        params, method = xmlrpclib.loads(data)
+
+        # generate response
         try:
-            # get arguments
-            data = self.rfile.read(int(self.headers["content-length"]))
-            params, method = xmlrpclib.loads(data)
-
-            # generate response
-            try:
+            if dispatch_method is not None:
+                response = dispatch_method(method, params)
+            else:                
                 response = self._dispatch(method, params)
-                # wrap response in a singleton tuple
-                response = (response,)
-            except:
-                # report exception back to server
-                response = xmlrpclib.dumps(
-                    xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
-                    )
-            else:
-                response = xmlrpclib.dumps(response, methodresponse=1)
+            # wrap response in a singleton tuple
+            response = (response,)
+            response = xmlrpclib.dumps(response, methodresponse=1)
+        except Fault, fault:
+            response = xmlrpclib.dumps(fault)
         except:
-            # internal error, report as HTTP server error
-            self.send_response(500)
-            self.end_headers()
+            # report exception back to server
+            response = xmlrpclib.dumps(
+                xmlrpclib.Fault(1, "%s:%s" % (sys.exc_type, sys.exc_value))
+                )
+
+        return response
+
+    def system_listMethods(self):
+        """system.listMethods() => ['add', 'subtract', 'multiple']
+
+        Returns a list of the methods supported by the server."""
+        
+        methods = self.funcs.keys()
+        if self.instance is not None:
+            # Instance can implement _listMethod to return a list of
+            # methods
+            if hasattr(self.instance, '_listMethods'):
+                methods = remove_duplicates(
+                        methods + self.instance._listMethods()
+                    )
+            # if the instance has a _dispatch method then we
+            # don't have enough information to provide a list
+            # of methods
+            elif not hasattr(self.instance, '_dispatch'):
+                methods = remove_duplicates(
+                        methods + list_public_methods(self.instance)
+                    )
+        methods.sort()
+        return methods
+    
+    def system_methodSignature(self, method_name):
+        """system.methodSignature('add') => [double, int, int]
+
+        Returns a list describing the signiture of the method. In the
+        above example, the add method takes two integers as arguments
+        and returns a double result.
+
+        This server does NOT support system.methodSignature."""
+
+        # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
+        
+        return 'signatures not supported'
+
+    def system_methodHelp(self, method_name):
+        """system.methodHelp('add') => "Adds two integers together"
+
+        Returns a string containing documentation for the specified method."""
+        
+        method = None
+        if self.funcs.has_key(method_name):
+            method = self.funcs[method_name]
+        elif self.instance is not None:
+            # Instance can implement _methodHelp to return help for a method
+            if hasattr(self.instance, '_methodHelp'):
+                return self.instance._methodHelp(method_name)
+            # if the instance has a _dispatch method then we
+            # don't have enough information to provide help
+            elif not hasattr(self.instance, '_dispatch'):
+                try:
+                    method = resolve_dotted_attribute(
+                                self.instance,
+                                method_name
+                                )
+                except AttributeError:
+                    pass
+
+        # Note that we aren't checking that the method actually
+        # be a callable object of some kind
+        if method is None:
+            return ""
         else:
-            # got a valid XML RPC response
-            self.send_response(200)
-            self.send_header("Content-type", "text/xml")
-            self.send_header("Content-length", str(len(response)))
-            self.end_headers()
-            self.wfile.write(response)
+            return pydoc.getdoc(method)
 
-            # shut down the connection
-            self.wfile.flush()
-            self.connection.shutdown(1)
+    def system_multicall(self, call_list):
+        """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
+[[4], ...]
 
+        Allows the caller to package multiple XML-RPC calls into a single
+        request.
+
+        See http://www.xmlrpc.com/discuss/msgReader$1208        
+        """
+        
+        results = []
+        for call in call_list:
+            method_name = call['methodName']
+            params = call['params']
+
+            try:
+                # XXX A marshalling error in any response will fail the entire
+                # multicall. If someone cares they should fix this.
+                results.append([self._dispatch(method_name, params)])
+            except Fault, fault:
+                results.append(
+                    {'faultCode' : fault.faultCode,
+                     'faultString' : fault.faultString}
+                    )
+            except:
+                results.append(
+                    {'faultCode' : 1,
+                     'faultString' : "%s:%s" % (sys.exc_type, sys.exc_value)}
+                    )
+        return results
+    
     def _dispatch(self, method, params):
         """Dispatches the XML-RPC method.
 
@@ -144,105 +356,155 @@
         and, if found, will be called.
 
         Methods beginning with an '_' are considered private and will
-        not be called by SimpleXMLRPCServer.
+        not be called.
         """
 
         func = None
         try:
             # check to see if a matching function has been registered
-            func = self.server.funcs[method]
+            func = self.funcs[method]
         except KeyError:
-            if self.server.instance is not None:
+            if self.instance is not None:
                 # check for a _dispatch method
-                if hasattr(self.server.instance, '_dispatch'):
-                    return self.server.instance._dispatch(method, params)
+                if hasattr(self.instance, '_dispatch'):
+                    return self.instance._dispatch(method, params)
                 else:
                     # call instance method directly
                     try:
-                        func = _resolve_dotted_attribute(
-                            self.server.instance,
+                        func = resolve_dotted_attribute(
+                            self.instance,
                             method
                             )
                     except AttributeError:
                         pass
 
         if func is not None:
-            return apply(func, params)
+            return func(*params)
         else:
             raise Exception('method "%s" is not supported' % method)
+        
+class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
+    """Simple XML-RPC request handler class.
 
+    Handles all HTTP POST requests and attempts to decode them as
+    XML-RPC requests.
+    """
+
+    def do_POST(self):
+        """Handles the HTTP POST request.
+
+        Attempts to interpret all HTTP POST requests as XML-RPC calls,
+        which are forwarded to the server's _dispatch method for handling.
+        """
+        
+        try:
+            # get arguments
+            data = self.rfile.read(int(self.headers["content-length"]))
+            # In previous versions of SimpleXMLRPCServer, _dispatch
+            # could be overridden in this class, instead of in
+            # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
+            # check to see if a subclass implements _dispatch and dispatch
+            # using that method if present.
+            response = self.server._marshaled_dispatch(
+                    data, getattr(self, '_dispatch', None)
+                )
+        except: # This should only happen if the module is buggy
+            # internal error, report as HTTP server error
+            self.send_response(500)
+            self.end_headers()
+        else:
+            # got a valid XML RPC response
+            self.send_response(200)
+            self.send_header("Content-type", "text/xml")
+            self.send_header("Content-length", str(len(response)))
+            self.end_headers()
+            self.wfile.write(response)
+
+            # shut down the connection
+            self.wfile.flush()
+            self.connection.shutdown(1)
+            
     def log_request(self, code='-', size='-'):
         """Selectively log an accepted request."""
 
         if self.server.logRequests:
             BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
 
-
-def _resolve_dotted_attribute(obj, attr):
-    """Resolves a dotted attribute name to an object.  Raises
-    an AttributeError if any attribute in the chain starts with a '_'.
-    """
-    for i in attr.split('.'):
-        if i.startswith('_'):
-            raise AttributeError(
-                'attempt to access private attribute "%s"' % i
-                )
-        else:
-            obj = getattr(obj,i)
-    return obj
-
-
-class SimpleXMLRPCServer(SocketServer.TCPServer):
+class SimpleXMLRPCServer(SocketServer.TCPServer, 
+                         SimpleXMLRPCDispatcher):
     """Simple XML-RPC server.
 
     Simple XML-RPC server that allows functions and a single instance
-    to be installed to handle requests.
+    to be installed to handle requests. The default implementation
+    attempts to dispatch XML-RPC calls to the functions or instance
+    installed in the server. Override the _dispatch method inhereted
+    from SimpleXMLRPCDispatcher to change this behavior.
     """
 
     def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
                  logRequests=1):
-        self.funcs = {}
         self.logRequests = logRequests
-        self.instance = None
+        
+        SimpleXMLRPCDispatcher.__init__(self)
         SocketServer.TCPServer.__init__(self, addr, requestHandler)
+        
+class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
+    """Simple handler for XML-RPC data passed through CGI."""
+    
+    def __init__(self):
+        SimpleXMLRPCDispatcher.__init__(self)
 
-    def register_instance(self, instance):
-        """Registers an instance to respond to XML-RPC requests.
+    def handle_xmlrpc(self, request_text):
+        """Handle a single XML-RPC request"""
+        
+        response = self._marshaled_dispatch(request_text)
+    
+        print 'Content-Type: text/xml'
+        print 'Content-Length: %d' % len(response)
+        print
+        print response
 
-        Only one instance can be installed at a time.
+    def handle_get(self):
+        """Handle a single HTTP GET request.
 
-        If the registered instance has a _dispatch method then that
-        method will be called with the name of the XML-RPC method and
-        it's parameters as a tuple
-        e.g. instance._dispatch('add',(2,3))
-
-        If the registered instance does not have a _dispatch method
-        then the instance will be searched to find a matching method
-        and, if found, will be called.
-
-        Methods beginning with an '_' are considered private and will
-        not be called by SimpleXMLRPCServer.
-
-        If a registered function matches a XML-RPC request, then it
-        will be called instead of the registered instance.
+        Default implementation indicates an error because
+        XML-RPC uses the POST method.
         """
 
-        self.instance = instance
-
-    def register_function(self, function, name = None):
-        """Registers a function to respond to XML-RPC requests.
-
-        The optional name argument can be used to set a Unicode name
-        for the function.
-
-        If an instance is also registered then it will only be called
-        if a matching function is not found.
+        code = 400
+        message, explain = \
+                 BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
+        
+        response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
+            {
+             'code' : code, 
+             'message' : message, 
+             'explain' : explain
+            }
+        print 'Status: %d %s' % (code, message)
+        print 'Content-Type: text/html'
+        print 'Content-Length: %d' % len(response)
+        print
+        print response
+                    
+    def handle_request(self, request_text = None):
+        """Handle a single XML-RPC request passed through a CGI post method.
+        
+        If no XML data is given then it is read from stdin. The resulting
+        XML-RPC response is printed to stdout along with the correct HTTP
+        headers.
         """
+        
+        if request_text is None and \
+            os.environ.get('REQUEST_METHOD', None) == 'GET':
+            self.handle_get()
+        else:
+            # POST data is normally available through stdin
+            if request_text is None:
+                request_text = sys.stdin.read()        
 
-        if name is None:
-            name = function.__name__
-        self.funcs[name] = function
-
+            self.handle_xmlrpc(request_text)
+        
 if __name__ == '__main__':
     server = SimpleXMLRPCServer(("localhost", 8000))
     server.register_function(pow)