autopep8
[sfa.git] / sfa / server / xmlrpcapi.py
1 #
2 # SFA XML-RPC and SOAP interfaces
3 #
4
5 import string
6
7 # SOAP support is optional
8 try:
9     import SOAPpy
10     from SOAPpy.Parser import parseSOAPRPC
11     from SOAPpy.Types import faultType
12     from SOAPpy.NS import NS
13     from SOAPpy.SOAPBuilder import buildSOAP
14 except ImportError:
15     SOAPpy = None
16
17 ####################
18 #from sfa.util.faults import SfaNotImplemented, SfaAPIError, SfaInvalidAPIMethod, SfaFault
19 from sfa.util.faults import SfaInvalidAPIMethod, SfaAPIError, SfaFault
20 from sfa.util.sfalogging import logger
21 from sfa.util.py23 import xmlrpc_client
22
23 ####################
24 # See "2.2 Characters" in the XML specification:
25 #
26 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
27 # avoiding
28 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
29
30 invalid_xml_ascii = map(chr, range(0x0, 0x8) + [0xB, 0xC] + range(0xE, 0x1F))
31 xml_escape_table = string.maketrans(
32     "".join(invalid_xml_ascii), "?" * len(invalid_xml_ascii))
33
34
35 def xmlrpclib_escape(s, replace=string.replace):
36     """
37     xmlrpclib does not handle invalid 7-bit control characters. This
38     function augments xmlrpclib.escape, which by default only replaces
39     '&', '<', and '>' with entities.
40     """
41
42     # This is the standard xmlrpclib.escape function
43     s = replace(s, "&", "&amp;")
44     s = replace(s, "<", "&lt;")
45     s = replace(s, ">", "&gt;",)
46
47     # Replace invalid 7-bit control characters with '?'
48     return s.translate(xml_escape_table)
49
50
51 def xmlrpclib_dump(self, value, write):
52     """
53     xmlrpclib cannot marshal instances of subclasses of built-in
54     types. This function overrides xmlrpclib.Marshaller.__dump so that
55     any value that is an instance of one of its acceptable types is
56     marshalled as that type.
57
58     xmlrpclib also cannot handle invalid 7-bit control characters. See
59     above.
60     """
61
62     # Use our escape function
63     args = [self, value, write]
64     if isinstance(value, (str, unicode)):
65         args.append(xmlrpclib_escape)
66
67     try:
68         # Try for an exact match first
69         f = self.dispatch[type(value)]
70     except KeyError:
71         raise
72         # Try for an isinstance() match
73         for Type, f in self.dispatch.iteritems():
74             if isinstance(value, Type):
75                 f(*args)
76                 return
77         raise TypeError("cannot marshal %s objects" % type(value))
78     else:
79         f(*args)
80
81 # You can't hide from me!
82 # Note: not quite  sure if this will still cause
83 # the expected behaviour under python3
84 xmlrpc_client.Marshaller._Marshaller__dump = xmlrpclib_dump
85
86
87 class XmlrpcApi:
88     """
89     The XmlrpcApi class implements a basic xmlrpc (or soap) service 
90     """
91
92     protocol = None
93
94     def __init__(self, encoding="utf-8", methods='sfa.methods'):
95
96         self.encoding = encoding
97         self.source = None
98
99         # flat list of method names
100         self.methods_module = methods_module = __import__(
101             methods, fromlist=[methods])
102         self.methods = methods_module.all
103
104         self.logger = logger
105
106     def callable(self, method):
107         """
108         Return a new instance of the specified method.
109         """
110         # Look up method
111         if method not in self.methods:
112             raise SfaInvalidAPIMethod(method)
113
114         # Get new instance of method
115         try:
116             classname = method.split(".")[-1]
117             module = __import__(self.methods_module.__name__ +
118                                 "." + method, globals(), locals(), [classname])
119             callablemethod = getattr(module, classname)(self)
120             return getattr(module, classname)(self)
121         except (ImportError, AttributeError):
122             self.logger.log_exc("Error importing method: %s" % method)
123             raise SfaInvalidAPIMethod(method)
124
125     def call(self, source, method, *args):
126         """
127         Call the named method from the specified source with the
128         specified arguments.
129         """
130         function = self.callable(method)
131         function.source = source
132         self.source = source
133         return function(*args)
134
135     def handle(self, source, data, method_map):
136         """
137         Handle an XML-RPC or SOAP request from the specified source.
138         """
139         # Parse request into method name and arguments
140         try:
141             interface = xmlrpc_client
142             self.protocol = 'xmlrpc'
143             (args, method) = xmlrpc_client.loads(data)
144             if method in method_map:
145                 method = method_map[method]
146             methodresponse = True
147
148         except Exception as e:
149             if SOAPpy is not None:
150                 self.protocol = 'soap'
151                 interface = SOAPpy
152                 (r, header, body, attrs) = parseSOAPRPC(
153                     data, header=1, body=1, attrs=1)
154                 method = r._name
155                 args = r._aslist()
156                 # XXX Support named arguments
157             else:
158                 raise e
159
160         try:
161             result = self.call(source, method, *args)
162         except SfaFault as fault:
163             result = fault
164             self.logger.log_exc("XmlrpcApi.handle has caught Exception")
165         except Exception as fault:
166             self.logger.log_exc("XmlrpcApi.handle has caught Exception")
167             result = SfaAPIError(fault)
168
169         # Return result
170         response = self.prepare_response(result, method)
171         return response
172
173     def prepare_response(self, result, method=""):
174         """
175         convert result to a valid xmlrpc or soap response
176         """
177
178         if self.protocol == 'xmlrpc':
179             if not isinstance(result, SfaFault):
180                 result = (result,)
181             response = xmlrpc_client.dumps(
182                 result, methodresponse=True, encoding=self.encoding, allow_none=1)
183         elif self.protocol == 'soap':
184             if isinstance(result, Exception):
185                 result = faultParameter(
186                     NS.ENV_T + ":Server", "Method Failed", method)
187                 result._setDetail("Fault %d: %s" %
188                                   (result.faultCode, result.faultString))
189             else:
190                 response = buildSOAP(
191                     kw={'%sResponse' % method: {'Result': result}}, encoding=self.encoding)
192         else:
193             if isinstance(result, Exception):
194                 raise result
195
196         return response