[FIX] remove openerplib.openerplib
[odoo/odoo.git] / python25-compat / SimpleXMLRPCServer.py
1 """Simple XML-RPC Server.
2
3 This module can be used to create simple XML-RPC servers
4 by creating a server and either installing functions, a
5 class instance, or by extending the SimpleXMLRPCServer
6 class.
7
8 It can also be used to handle XML-RPC requests in a CGI
9 environment using CGIXMLRPCRequestHandler.
10
11 A list of possible usage patterns follows:
12
13 1. Install functions:
14
15 server = SimpleXMLRPCServer(("localhost", 8000))
16 server.register_function(pow)
17 server.register_function(lambda x,y: x+y, 'add')
18 server.serve_forever()
19
20 2. Install an instance:
21
22 class MyFuncs:
23     def __init__(self):
24         # make all of the string functions available through
25         # string.func_name
26         import string
27         self.string = string
28     def _listMethods(self):
29         # implement this method so that system.listMethods
30         # knows to advertise the strings methods
31         return list_public_methods(self) + \
32                 ['string.' + method for method in list_public_methods(self.string)]
33     def pow(self, x, y): return pow(x, y)
34     def add(self, x, y) : return x + y
35
36 server = SimpleXMLRPCServer(("localhost", 8000))
37 server.register_introspection_functions()
38 server.register_instance(MyFuncs())
39 server.serve_forever()
40
41 3. Install an instance with custom dispatch method:
42
43 class Math:
44     def _listMethods(self):
45         # this method must be present for system.listMethods
46         # to work
47         return ['add', 'pow']
48     def _methodHelp(self, method):
49         # this method must be present for system.methodHelp
50         # to work
51         if method == 'add':
52             return "add(2,3) => 5"
53         elif method == 'pow':
54             return "pow(x, y[, z]) => number"
55         else:
56             # By convention, return empty
57             # string if no help is available
58             return ""
59     def _dispatch(self, method, params):
60         if method == 'pow':
61             return pow(*params)
62         elif method == 'add':
63             return params[0] + params[1]
64         else:
65             raise 'bad method'
66
67 server = SimpleXMLRPCServer(("localhost", 8000))
68 server.register_introspection_functions()
69 server.register_instance(Math())
70 server.serve_forever()
71
72 4. Subclass SimpleXMLRPCServer:
73
74 class MathServer(SimpleXMLRPCServer):
75     def _dispatch(self, method, params):
76         try:
77             # We are forcing the 'export_' prefix on methods that are
78             # callable through XML-RPC to prevent potential security
79             # problems
80             func = getattr(self, 'export_' + method)
81         except AttributeError:
82             raise Exception('method "%s" is not supported' % method)
83         else:
84             return func(*params)
85
86     def export_add(self, x, y):
87         return x + y
88
89 server = MathServer(("localhost", 8000))
90 server.serve_forever()
91
92 5. CGI script:
93
94 server = CGIXMLRPCRequestHandler()
95 server.register_function(pow)
96 server.handle_request()
97 """
98
99 # Written by Brian Quinlan (brian@sweetapp.com).
100 # Based on code written by Fredrik Lundh.
101
102 import xmlrpclib
103 from xmlrpclib import Fault
104 import SocketServer
105 import BaseHTTPServer
106 import sys
107 import os
108 import traceback
109 try:
110     import fcntl
111 except ImportError:
112     fcntl = None
113
114 def resolve_dotted_attribute(obj, attr, allow_dotted_names=True):
115     """resolve_dotted_attribute(a, 'b.c.d') => a.b.c.d
116
117     Resolves a dotted attribute name to an object.  Raises
118     an AttributeError if any attribute in the chain starts with a '_'.
119
120     If the optional allow_dotted_names argument is false, dots are not
121     supported and this function operates similar to getattr(obj, attr).
122     """
123
124     if allow_dotted_names:
125         attrs = attr.split('.')
126     else:
127         attrs = [attr]
128
129     for i in attrs:
130         if i.startswith('_'):
131             raise AttributeError(
132                 'attempt to access private attribute "%s"' % i
133                 )
134         else:
135             obj = getattr(obj,i)
136     return obj
137
138 def list_public_methods(obj):
139     """Returns a list of attribute strings, found in the specified
140     object, which represent callable attributes"""
141
142     return [member for member in dir(obj)
143                 if not member.startswith('_') and
144                     hasattr(getattr(obj, member), '__call__')]
145
146 def remove_duplicates(lst):
147     """remove_duplicates([2,2,2,1,3,3]) => [3,1,2]
148
149     Returns a copy of a list without duplicates. Every list
150     item must be hashable and the order of the items in the
151     resulting list is not defined.
152     """
153     u = {}
154     for x in lst:
155         u[x] = 1
156
157     return u.keys()
158
159 class SimpleXMLRPCDispatcher:
160     """Mix-in class that dispatches XML-RPC requests.
161
162     This class is used to register XML-RPC method handlers
163     and then to dispatch them. There should never be any
164     reason to instantiate this class directly.
165     """
166
167     def __init__(self, allow_none, encoding):
168         self.funcs = {}
169         self.instance = None
170         self.allow_none = allow_none
171         self.encoding = encoding
172
173     def register_instance(self, instance, allow_dotted_names=False):
174         """Registers an instance to respond to XML-RPC requests.
175
176         Only one instance can be installed at a time.
177
178         If the registered instance has a _dispatch method then that
179         method will be called with the name of the XML-RPC method and
180         its parameters as a tuple
181         e.g. instance._dispatch('add',(2,3))
182
183         If the registered instance does not have a _dispatch method
184         then the instance will be searched to find a matching method
185         and, if found, will be called. Methods beginning with an '_'
186         are considered private and will not be called by
187         SimpleXMLRPCServer.
188
189         If a registered function matches a XML-RPC request, then it
190         will be called instead of the registered instance.
191
192         If the optional allow_dotted_names argument is true and the
193         instance does not have a _dispatch method, method names
194         containing dots are supported and resolved, as long as none of
195         the name segments start with an '_'.
196
197             *** SECURITY WARNING: ***
198
199             Enabling the allow_dotted_names options allows intruders
200             to access your module's global variables and may allow
201             intruders to execute arbitrary code on your machine.  Only
202             use this option on a secure, closed network.
203
204         """
205
206         self.instance = instance
207         self.allow_dotted_names = allow_dotted_names
208
209     def register_function(self, function, name = None):
210         """Registers a function to respond to XML-RPC requests.
211
212         The optional name argument can be used to set a Unicode name
213         for the function.
214         """
215
216         if name is None:
217             name = function.__name__
218         self.funcs[name] = function
219
220     def register_introspection_functions(self):
221         """Registers the XML-RPC introspection methods in the system
222         namespace.
223
224         see http://xmlrpc.usefulinc.com/doc/reserved.html
225         """
226
227         self.funcs.update({'system.listMethods' : self.system_listMethods,
228                       'system.methodSignature' : self.system_methodSignature,
229                       'system.methodHelp' : self.system_methodHelp})
230
231     def register_multicall_functions(self):
232         """Registers the XML-RPC multicall method in the system
233         namespace.
234
235         see http://www.xmlrpc.com/discuss/msgReader$1208"""
236
237         self.funcs.update({'system.multicall' : self.system_multicall})
238
239     def _marshaled_dispatch(self, data, dispatch_method = None):
240         """Dispatches an XML-RPC method from marshalled (XML) data.
241
242         XML-RPC methods are dispatched from the marshalled (XML) data
243         using the _dispatch method and the result is returned as
244         marshalled data. For backwards compatibility, a dispatch
245         function can be provided as an argument (see comment in
246         SimpleXMLRPCRequestHandler.do_POST) but overriding the
247         existing method through subclassing is the prefered means
248         of changing method dispatch behavior.
249         """
250
251         try:
252             params, method = xmlrpclib.loads(data)
253
254             # generate response
255             if dispatch_method is not None:
256                 response = dispatch_method(method, params)
257             else:
258                 response = self._dispatch(method, params)
259             # wrap response in a singleton tuple
260             response = (response,)
261             response = xmlrpclib.dumps(response, methodresponse=1,
262                                        allow_none=self.allow_none, encoding=self.encoding)
263         except Fault, fault:
264             response = xmlrpclib.dumps(fault, allow_none=self.allow_none,
265                                        encoding=self.encoding)
266         except:
267             # report exception back to server
268             exc_type, exc_value, exc_tb = sys.exc_info()
269             response = xmlrpclib.dumps(
270                 xmlrpclib.Fault(1, "%s:%s" % (exc_type, exc_value)),
271                 encoding=self.encoding, allow_none=self.allow_none,
272                 )
273
274         return response
275
276     def system_listMethods(self):
277         """system.listMethods() => ['add', 'subtract', 'multiple']
278
279         Returns a list of the methods supported by the server."""
280
281         methods = self.funcs.keys()
282         if self.instance is not None:
283             # Instance can implement _listMethod to return a list of
284             # methods
285             if hasattr(self.instance, '_listMethods'):
286                 methods = remove_duplicates(
287                         methods + self.instance._listMethods()
288                     )
289             # if the instance has a _dispatch method then we
290             # don't have enough information to provide a list
291             # of methods
292             elif not hasattr(self.instance, '_dispatch'):
293                 methods = remove_duplicates(
294                         methods + list_public_methods(self.instance)
295                     )
296         methods.sort()
297         return methods
298
299     def system_methodSignature(self, method_name):
300         """system.methodSignature('add') => [double, int, int]
301
302         Returns a list describing the signature of the method. In the
303         above example, the add method takes two integers as arguments
304         and returns a double result.
305
306         This server does NOT support system.methodSignature."""
307
308         # See http://xmlrpc.usefulinc.com/doc/sysmethodsig.html
309
310         return 'signatures not supported'
311
312     def system_methodHelp(self, method_name):
313         """system.methodHelp('add') => "Adds two integers together"
314
315         Returns a string containing documentation for the specified method."""
316
317         method = None
318         if method_name in self.funcs:
319             method = self.funcs[method_name]
320         elif self.instance is not None:
321             # Instance can implement _methodHelp to return help for a method
322             if hasattr(self.instance, '_methodHelp'):
323                 return self.instance._methodHelp(method_name)
324             # if the instance has a _dispatch method then we
325             # don't have enough information to provide help
326             elif not hasattr(self.instance, '_dispatch'):
327                 try:
328                     method = resolve_dotted_attribute(
329                                 self.instance,
330                                 method_name,
331                                 self.allow_dotted_names
332                                 )
333                 except AttributeError:
334                     pass
335
336         # Note that we aren't checking that the method actually
337         # be a callable object of some kind
338         if method is None:
339             return ""
340         else:
341             import pydoc
342             return pydoc.getdoc(method)
343
344     def system_multicall(self, call_list):
345         """system.multicall([{'methodName': 'add', 'params': [2, 2]}, ...]) => \
346 [[4], ...]
347
348         Allows the caller to package multiple XML-RPC calls into a single
349         request.
350
351         See http://www.xmlrpc.com/discuss/msgReader$1208
352         """
353
354         results = []
355         for call in call_list:
356             method_name = call['methodName']
357             params = call['params']
358
359             try:
360                 # XXX A marshalling error in any response will fail the entire
361                 # multicall. If someone cares they should fix this.
362                 results.append([self._dispatch(method_name, params)])
363             except Fault, fault:
364                 results.append(
365                     {'faultCode' : fault.faultCode,
366                      'faultString' : fault.faultString}
367                     )
368             except:
369                 exc_type, exc_value, exc_tb = sys.exc_info()
370                 results.append(
371                     {'faultCode' : 1,
372                      'faultString' : "%s:%s" % (exc_type, exc_value)}
373                     )
374         return results
375
376     def _dispatch(self, method, params):
377         """Dispatches the XML-RPC method.
378
379         XML-RPC calls are forwarded to a registered function that
380         matches the called XML-RPC method name. If no such function
381         exists then the call is forwarded to the registered instance,
382         if available.
383
384         If the registered instance has a _dispatch method then that
385         method will be called with the name of the XML-RPC method and
386         its parameters as a tuple
387         e.g. instance._dispatch('add',(2,3))
388
389         If the registered instance does not have a _dispatch method
390         then the instance will be searched to find a matching method
391         and, if found, will be called.
392
393         Methods beginning with an '_' are considered private and will
394         not be called.
395         """
396
397         func = None
398         try:
399             # check to see if a matching function has been registered
400             func = self.funcs[method]
401         except KeyError:
402             if self.instance is not None:
403                 # check for a _dispatch method
404                 if hasattr(self.instance, '_dispatch'):
405                     return self.instance._dispatch(method, params)
406                 else:
407                     # call instance method directly
408                     try:
409                         func = resolve_dotted_attribute(
410                             self.instance,
411                             method,
412                             self.allow_dotted_names
413                             )
414                     except AttributeError:
415                         pass
416
417         if func is not None:
418             return func(*params)
419         else:
420             raise Exception('method "%s" is not supported' % method)
421
422 class SimpleXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
423     """Simple XML-RPC request handler class.
424
425     Handles all HTTP POST requests and attempts to decode them as
426     XML-RPC requests.
427     """
428
429     # Class attribute listing the accessible path components;
430     # paths not on this list will result in a 404 error.
431     rpc_paths = ('/', '/RPC2')
432
433     def is_rpc_path_valid(self):
434         if self.rpc_paths:
435             return self.path in self.rpc_paths
436         else:
437             # If .rpc_paths is empty, just assume all paths are legal
438             return True
439
440     def do_POST(self):
441         """Handles the HTTP POST request.
442
443         Attempts to interpret all HTTP POST requests as XML-RPC calls,
444         which are forwarded to the server's _dispatch method for handling.
445         """
446
447         # Check that the path is legal
448         if not self.is_rpc_path_valid():
449             self.report_404()
450             return
451
452         try:
453             # Get arguments by reading body of request.
454             # We read this in chunks to avoid straining
455             # socket.read(); around the 10 or 15Mb mark, some platforms
456             # begin to have problems (bug #792570).
457             max_chunk_size = 10*1024*1024
458             size_remaining = int(self.headers["content-length"])
459             L = []
460             while size_remaining:
461                 chunk_size = min(size_remaining, max_chunk_size)
462                 L.append(self.rfile.read(chunk_size))
463                 size_remaining -= len(L[-1])
464             data = ''.join(L)
465
466             # In previous versions of SimpleXMLRPCServer, _dispatch
467             # could be overridden in this class, instead of in
468             # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
469             # check to see if a subclass implements _dispatch and dispatch
470             # using that method if present.
471             response = self.server._marshaled_dispatch(
472                     data, getattr(self, '_dispatch', None)
473                 )
474         except Exception, e: # This should only happen if the module is buggy
475             # internal error, report as HTTP server error
476             self.send_response(500)
477
478             # Send information about the exception if requested
479             if hasattr(self.server, '_send_traceback_header') and \
480                     self.server._send_traceback_header:
481                 self.send_header("X-exception", str(e))
482                 self.send_header("X-traceback", traceback.format_exc())
483
484             self.end_headers()
485         else:
486             # got a valid XML RPC response
487             self.send_response(200)
488             self.send_header("Content-type", "text/xml")
489             self.send_header("Content-length", str(len(response)))
490             self.end_headers()
491             self.wfile.write(response)
492
493             # shut down the connection
494             self.wfile.flush()
495             self.connection.shutdown(1)
496
497     def report_404 (self):
498             # Report a 404 error
499         self.send_response(404)
500         response = 'No such page'
501         self.send_header("Content-type", "text/plain")
502         self.send_header("Content-length", str(len(response)))
503         self.end_headers()
504         self.wfile.write(response)
505         # shut down the connection
506         self.wfile.flush()
507         self.connection.shutdown(1)
508
509     def log_request(self, code='-', size='-'):
510         """Selectively log an accepted request."""
511
512         if self.server.logRequests:
513             BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)
514
515 class SimpleXMLRPCServer(SocketServer.TCPServer,
516                          SimpleXMLRPCDispatcher):
517     """Simple XML-RPC server.
518
519     Simple XML-RPC server that allows functions and a single instance
520     to be installed to handle requests. The default implementation
521     attempts to dispatch XML-RPC calls to the functions or instance
522     installed in the server. Override the _dispatch method inhereted
523     from SimpleXMLRPCDispatcher to change this behavior.
524     """
525
526     allow_reuse_address = True
527
528     # Warning: this is for debugging purposes only! Never set this to True in
529     # production code, as will be sending out sensitive information (exception
530     # and stack trace details) when exceptions are raised inside
531     # SimpleXMLRPCRequestHandler.do_POST
532     _send_traceback_header = False
533
534     def __init__(self, addr, requestHandler=SimpleXMLRPCRequestHandler,
535                  logRequests=True, allow_none=False, encoding=None, bind_and_activate=True):
536         self.logRequests = logRequests
537
538         SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
539         SocketServer.TCPServer.__init__(self, addr, requestHandler, bind_and_activate)
540
541         # [Bug #1222790] If possible, set close-on-exec flag; if a
542         # method spawns a subprocess, the subprocess shouldn't have
543         # the listening socket open.
544         if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
545             flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
546             flags |= fcntl.FD_CLOEXEC
547             fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
548
549 class CGIXMLRPCRequestHandler(SimpleXMLRPCDispatcher):
550     """Simple handler for XML-RPC data passed through CGI."""
551
552     def __init__(self, allow_none=False, encoding=None):
553         SimpleXMLRPCDispatcher.__init__(self, allow_none, encoding)
554
555     def handle_xmlrpc(self, request_text):
556         """Handle a single XML-RPC request"""
557
558         response = self._marshaled_dispatch(request_text)
559
560         print 'Content-Type: text/xml'
561         print 'Content-Length: %d' % len(response)
562         print
563         sys.stdout.write(response)
564
565     def handle_get(self):
566         """Handle a single HTTP GET request.
567
568         Default implementation indicates an error because
569         XML-RPC uses the POST method.
570         """
571
572         code = 400
573         message, explain = \
574                  BaseHTTPServer.BaseHTTPRequestHandler.responses[code]
575
576         response = BaseHTTPServer.DEFAULT_ERROR_MESSAGE % \
577             {
578              'code' : code,
579              'message' : message,
580              'explain' : explain
581             }
582         print 'Status: %d %s' % (code, message)
583         print 'Content-Type: text/html'
584         print 'Content-Length: %d' % len(response)
585         print
586         sys.stdout.write(response)
587
588     def handle_request(self, request_text = None):
589         """Handle a single XML-RPC request passed through a CGI post method.
590
591         If no XML data is given then it is read from stdin. The resulting
592         XML-RPC response is printed to stdout along with the correct HTTP
593         headers.
594         """
595
596         if request_text is None and \
597             os.environ.get('REQUEST_METHOD', None) == 'GET':
598             self.handle_get()
599         else:
600             # POST data is normally available through stdin
601             if request_text is None:
602                 request_text = sys.stdin.read()
603
604             self.handle_xmlrpc(request_text)
605
606 if __name__ == '__main__':
607     print 'Running XML-RPC server on port 8000'
608     server = SimpleXMLRPCServer(("localhost", 8000))
609     server.register_function(pow)
610     server.register_function(lambda x,y: x+y, 'add')
611     server.serve_forever()