2 # SFA XML-RPC and SOAP interfaces
5 from importlib import import_module
7 # SOAP support is optional
10 from SOAPpy.Parser import parseSOAPRPC
11 from SOAPpy.Types import faultType
12 from SOAPpy.NS import NS
13 from SOAPpy.SOAPBuilder import buildSOAP
18 from sfa.util.faults import SfaInvalidAPIMethod, SfaAPIError, SfaFault
19 from sfa.util.sfalogging import logger
20 from sfa.util.py23 import xmlrpc_client
23 # See "2.2 Characters" in the XML specification:
25 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
27 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
29 invalid_codepoints = list(range(0x0, 0x8)) + [0xB, 0xC] + list(range(0xE, 0x1F))
30 # broke with f24, somehow we get a unicode
31 # as an incoming string to be translated
32 str_xml_escape_table = \
33 str.maketrans("".join((chr(x) for x in invalid_codepoints)),
34 "?" * len(invalid_codepoints))
35 # loosely inspired from
36 # http://stackoverflow.com/questions/1324067/
37 # how-do-i-get-str-translate-to-work-with-unicode-strings
38 unicode_xml_escape_table = \
39 {invalid: "?" for invalid in invalid_codepoints}
42 def xmlrpclib_escape(s, replace=str.replace):
44 xmlrpclib does not handle invalid 7-bit control characters. This
45 function augments xmlrpclib.escape, which by default only replaces
46 '&', '<', and '>' with entities.
49 # This is the standard xmlrpclib.escape function
50 s = replace(s, "&", "&")
51 s = replace(s, "<", "<")
52 s = replace(s, ">", ">",)
54 # Replace invalid 7-bit control characters with '?'
55 if isinstance(s, str):
56 return s.translate(str_xml_escape_table)
58 return s.translate(unicode_xml_escape_table)
61 def xmlrpclib_dump(self, value, write):
63 xmlrpclib cannot marshal instances of subclasses of built-in
64 types. This function overrides xmlrpclib.Marshaller.__dump so that
65 any value that is an instance of one of its acceptable types is
66 marshalled as that type.
68 xmlrpclib also cannot handle invalid 7-bit control characters. See
72 # Use our escape function
73 args = [self, value, write]
74 if isinstance(value, str):
75 args.append(xmlrpclib_escape)
78 # Try for an exact match first
79 f = self.dispatch[type(value)]
81 # Try for an isinstance() match
82 for Type, f in self.dispatch.items():
83 if isinstance(value, Type):
86 raise TypeError("cannot marshal %s objects" % type(value))
91 # You can't hide from me!
92 # Note: not quite sure if this will still cause
93 # the expected behaviour under python3
94 xmlrpc_client.Marshaller._Marshaller__dump = xmlrpclib_dump
99 The XmlrpcApi class implements a basic xmlrpc (or soap) service
104 def __init__(self, encoding="utf-8", methods='sfa.methods'):
106 self.encoding = encoding
109 # flat list of method names
110 self.methods_module = methods_module = import_module(methods)
111 self.methods = methods_module.all
113 def callable(self, method):
115 Return a new instance of the specified method.
118 if method not in self.methods:
119 raise SfaInvalidAPIMethod(method)
121 # Get new instance of method
123 classname = method.split(".")[-1]
124 module = import_module(
125 self.methods_module.__name__ + "." + method)
126 callablemethod = getattr(module, classname)(self)
127 return getattr(module, classname)(self)
128 except (ImportError, AttributeError):
129 logger.log_exc("Error importing method: %s" % method)
130 raise SfaInvalidAPIMethod(method)
132 def call(self, source, method, *args):
134 Call the named method from the specified source with the
137 function = self.callable(method)
138 function.source = source
140 return function(*args)
142 def handle(self, source, data, method_map):
144 Handle an XML-RPC or SOAP request from the specified source.
146 # Parse request into method name and arguments
148 interface = xmlrpc_client
149 self.protocol = 'xmlrpc'
150 (args, method) = xmlrpc_client.loads(data)
151 if method in method_map:
152 method = method_map[method]
153 methodresponse = True
155 except Exception as e:
156 if SOAPpy is not None:
157 self.protocol = 'soap'
159 (r, header, body, attrs) = parseSOAPRPC(
160 data, header=1, body=1, attrs=1)
163 # XXX Support named arguments
168 result = self.call(source, method, *args)
169 except SfaFault as fault:
171 logger.log_exc("XmlrpcApi.handle has caught Exception")
172 except Exception as fault:
173 logger.log_exc("XmlrpcApi.handle has caught Exception")
174 result = SfaAPIError(fault)
177 response = self.prepare_response(result, method)
180 def prepare_response(self, result, method=""):
182 convert result to a valid xmlrpc or soap response
185 if self.protocol == 'xmlrpc':
186 if not isinstance(result, SfaFault):
188 response = xmlrpc_client.dumps(
189 result, methodresponse=True, encoding=self.encoding, allow_none=1)
190 elif self.protocol == 'soap':
191 if isinstance(result, Exception):
192 result = faultParameter(
193 NS.ENV_T + ":Server", "Method Failed", method)
194 result._setDetail("Fault %d: %s" %
195 (result.faultCode, result.faultString))
197 response = buildSOAP(
198 kw={'%sResponse' % method: {'Result': result}}, encoding=self.encoding)
200 if isinstance(result, Exception):