1 # pylint: disable=c0111, c0103
3 # PLCAPI XML-RPC and SOAP interfaces
5 # Aaron Klingaman <alk@absarokasoft.com>
6 # Mark Huang <mlhuang@cs.princeton.edu>
8 # Copyright (C) 2004-2006 The Trustees of Princeton University
18 # See "2.2 Characters" in the XML specification:
20 # #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
22 # [#x7F-#x84], [#x86-#x9F], [#xFDD0-#xFDDF]
24 invalid_codepoints = list(range(0x0, 0x8)) + [0xB, 0xC] + list(range(0xE, 0x1F))
25 # broke with f24, somehow we get a unicode
26 # as an incoming string to be translated
27 str_xml_escape_table = \
28 str.maketrans("".join((chr(x) for x in invalid_codepoints)),
29 "?" * len(invalid_codepoints))
30 # loosely inspired from
31 # http://stackoverflow.com/questions/1324067/
32 # how-do-i-get-str-translate-to-work-with-unicode-strings
33 unicode_xml_escape_table = \
34 {invalid: "?" for invalid in invalid_codepoints}
37 def xmlrpclib_escape(s, replace=str.replace):
39 xmlrpclib does not handle invalid 7-bit control characters. This
40 function augments xmlrpclib.escape, which by default only replaces
41 '&', '<', and '>' with entities.
44 # This is the standard xmlrpclib.escape function
45 s = replace(s, "&", "&")
46 s = replace(s, "<", "<")
47 s = replace(s, ">", ">",)
49 # Replace invalid 7-bit control characters with '?'
50 if isinstance(s, str):
51 return s.translate(str_xml_escape_table)
53 return s.translate(unicode_xml_escape_table)
56 def test_xmlrpclib_escape():
59 "".join((chr(x) for x in range(128))),
60 # likewise but as a unicode string up to 256
61 "".join((chr(x) for x in range(256))),
63 for incoming in incomings:
64 print("==================== xmlrpclib_escape INPUT")
65 print(type(incoming), '->', incoming)
66 print("==================== xmlrpclib_escape OUTPUT")
67 print(xmlrpclib_escape(incoming))
70 def xmlrpclib_dump(self, value, write):
72 xmlrpclib cannot marshal instances of subclasses of built-in
73 types. This function overrides xmlrpclib.Marshaller.__dump so that
74 any value that is an instance of one of its acceptable types is
75 marshalled as that type.
77 xmlrpclib also cannot handle invalid 7-bit control characters. See
81 # Use our escape function
82 args = [self, value, write]
83 if isinstance(value, str):
84 args.append(xmlrpclib_escape)
87 # Try for an exact match first
88 f = self.dispatch[type(value)]
90 # Try for an isinstance() match
91 for Type, f in self.dispatch.items():
92 if isinstance(value, Type):
95 raise TypeError("cannot marshal %s objects" % type(value))
100 # You can't hide from me!
101 xmlrpc.client.Marshaller._Marshaller__dump = xmlrpclib_dump
103 # SOAP support is optional
106 from SOAPpy.Parser import parseSOAPRPC
107 from SOAPpy.NS import NS
108 from SOAPpy.SOAPBuilder import buildSOAP
112 from PLC.Config import Config
113 from PLC.Faults import *
118 def import_deep(name):
119 mod = __import__(name)
120 components = name.split('.')
121 for comp in components[1:]:
122 mod = getattr(mod, comp)
128 # flat list of method names
129 native_methods = PLC.Methods.native_methods
131 # other_methods_map : dict {methodname: fullpath}
132 # e.g. 'Accessors' -> 'PLC.Accessors.Accessors'
133 other_methods_map = {}
134 for subdir in ['Accessors']:
136 # scan e.g. PLC.Accessors.__all__
137 pkg = __import__(path).__dict__[subdir]
138 for modulename in getattr(pkg, "__all__"):
139 fullpath = path + "." + modulename
140 for method in getattr(import_deep(fullpath), "methods"):
141 other_methods_map[method] = fullpath
143 all_methods = native_methods + list(other_methods_map.keys())
145 def __init__(self, config="/etc/planetlab/plc_config",
147 self.encoding = encoding
149 # Better just be documenting the API
154 self.config = Config(config)
155 # print("config has keys {}"
156 # .format(vars(self.config).keys()))
158 # Initialize database connection
159 if self.config.PLC_DB_TYPE == "postgresql":
160 from PLC.PostgreSQL import PostgreSQL
161 self.db = PostgreSQL(self)
163 raise PLCAPIError("Unsupported database type "
164 + self.config.PLC_DB_TYPE)
166 # Enable Caching. Only for GetSlivers for the moment.
167 # TODO: we may consider to do this in an aspect like the ones above.
169 if self.config.PLC_GETSLIVERS_CACHE:
170 getslivers_cache = True
171 except AttributeError:
172 getslivers_cache = False
175 os.environ['DJANGO_SETTINGS_MODULE'] = 'plc_django_settings'
176 from cache_utils.decorators import cached
177 from PLC.Methods.GetSlivers import GetSlivers
180 def cacheable_call(cls, auth, node_id_or_hostname):
181 return cls.raw_call(auth, node_id_or_hostname)
183 GetSlivers.call = cacheable_call
185 def callable(self, method):
187 Return a new instance of the specified method.
190 if method not in self.all_methods:
191 raise PLCInvalidAPIMethod(method)
193 # Get new instance of method
195 classname = method.split(".")[-1]
196 if method in self.native_methods:
197 fullpath = "PLC.Methods." + method
199 fullpath = self.other_methods_map[method]
200 module = __import__(fullpath, globals(), locals(), [classname])
201 return getattr(module, classname)(self)
202 except (ImportError, AttributeError):
203 raise PLCInvalidAPIMethod("import error %s for %s"
204 % (AttributeError, fullpath))
206 def call(self, source, method, *args):
208 Call the named method from the specified source with the
212 function = self.callable(method)
213 function.source = source
214 return function(*args)
216 def handle(self, source, data):
218 Handle an XML-RPC or SOAP request from the specified source.
221 # Parse request into method name and arguments
223 interface = xmlrpc.client
224 (args, method) = xmlrpc.client.loads(data)
225 except Exception as exc:
226 if SOAPpy is not None:
228 (r, header, body, attrs) = \
229 parseSOAPRPC(data, header=1, body=1, attrs=1)
232 # XXX Support named arguments
237 result = self.call(source, method, *args)
238 except PLCFault as fault:
239 # Handle expected faults
240 if interface == xmlrpc.client:
242 methodresponse = None
243 elif interface == SOAPpy:
244 result = faultParameter(NS.ENV_T + ":Server",
245 "Method Failed", method)
246 result._setDetail("Fault %d: %s"
247 % (fault.faultCode, fault.faultString))
250 if interface == xmlrpc.client:
251 if not isinstance(result, PLCFault):
253 data = xmlrpc.client.dumps(result, methodresponse=True,
254 encoding=self.encoding, allow_none=1)
255 elif interface == SOAPpy:
257 kw={'%sResponse' % method: {'Result': result}},
258 encoding=self.encoding)
262 def handle_json(self, source, data):
264 Handle a JSON request
266 method, args = json.loads(data)
268 result = self.call(source, method, *args)
269 except Exception as exc:
272 return json.dumps(result)
274 # one simple unit test
275 if __name__ == '__main__':
276 test_xmlrpclib_escape()