1 #!/usr/bin/env python 2 3 """ 4 XML-RPC support using libxml2dom. 5 6 See: http://www.xmlrpc.com/spec 7 8 Copyright (C) 2007 Paul Boddie <paul@boddie.org.uk> 9 10 This program is free software; you can redistribute it and/or modify it under 11 the terms of the GNU Lesser General Public License as published by the Free 12 Software Foundation; either version 3 of the License, or (at your option) any 13 later version. 14 15 This program is distributed in the hope that it will be useful, but WITHOUT 16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 17 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 18 details. 19 20 You should have received a copy of the GNU Lesser General Public License along 21 with this program. If not, see <http://www.gnu.org/licenses/>. 22 23 -------- 24 25 The sending and receiving of XML-RPC messages can be done using traditional HTTP 26 libraries. 27 28 See tests/xmlrpc_test.py for more details. 29 """ 30 31 import libxml2dom 32 from libxml2dom.macrolib import * 33 from libxml2dom.macrolib import \ 34 createDocument as Node_createDocument 35 from libxml2dom.rpc import ParameterName, ParameterValue 36 37 class XMLRPCImplementation(libxml2dom.Implementation): 38 39 "Contains an XML-RPC-specific implementation." 40 41 # Wrapping of documents. 42 43 def adoptDocument(self, node): 44 return XMLRPCDocument(node, self) 45 46 # Factory functions. 47 48 def get_node(self, _node, context_node): 49 50 """ 51 Get a libxml2dom node for the given low-level '_node' and libxml2dom 52 'context_node'. 53 """ 54 55 if Node_nodeType(_node) == context_node.ELEMENT_NODE: 56 57 # Make special elements. 58 59 if Node_localName(_node) in ("methodCall", "methodResponse"): 60 return XMLRPCMethodElement(_node, self, context_node.ownerDocument) 61 elif Node_localName(_node) == "methodName": 62 return XMLRPCMethodNameElement(_node, self, context_node.ownerDocument) 63 elif Node_localName(_node) == "fault": 64 return XMLRPCFaultElement(_node, self, context_node.ownerDocument) 65 elif Node_localName(_node) == "string": 66 return XMLRPCStringElement(_node, self, context_node.ownerDocument) 67 elif Node_localName(_node) in ("int", "i4"): 68 return XMLRPCIntegerElement(_node, self, context_node.ownerDocument) 69 elif Node_localName(_node) == "boolean": 70 return XMLRPCBooleanElement(_node, self, context_node.ownerDocument) 71 elif Node_localName(_node) == "double": 72 return XMLRPCDoubleElement(_node, self, context_node.ownerDocument) 73 elif Node_localName(_node) == "dateTime.iso8601": 74 return XMLRPCDateTimeElement(_node, self, context_node.ownerDocument) 75 elif Node_localName(_node) == "base64": 76 return XMLRPCBase64Element(_node, self, context_node.ownerDocument) 77 elif Node_localName(_node) == "struct": 78 return XMLRPCStructElement(_node, self, context_node.ownerDocument) 79 elif Node_localName(_node) == "member": 80 return XMLRPCMemberElement(_node, self, context_node.ownerDocument) 81 elif Node_localName(_node) == "value": 82 return XMLRPCValueElement(_node, self, context_node.ownerDocument) 83 elif Node_localName(_node) == "name": 84 return XMLRPCNameElement(_node, self, context_node.ownerDocument) 85 elif Node_localName(_node) == "array": 86 return XMLRPCArrayElement(_node, self, context_node.ownerDocument) 87 elif Node_localName(_node) == "data": 88 return XMLRPCDataElement(_node, self, context_node.ownerDocument) 89 90 # Otherwise, make generic XML-RPC elements. 91 92 return XMLRPCElement(_node, self, context_node.ownerDocument) 93 94 else: 95 return libxml2dom.Implementation.get_node(self, _node, context_node) 96 97 # Convenience functions. 98 99 def createXMLRPCMessage(self, namespaceURI, localName): 100 101 "Create a new XML-RPC message document (fragment)." 102 103 return XMLRPCDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement 104 105 def createMethodCall(self): 106 return self.createXMLRPCMessage(None, "methodCall") 107 108 def createMethodResponse(self): 109 return self.createXMLRPCMessage(None, "methodResponse") 110 111 # Node classes. 112 113 class XMLRPCNode(libxml2dom.Node): 114 115 "Convenience modifications to nodes specific to libxml2dom.xmlrpc." 116 117 pass 118 119 class XMLRPCElement(XMLRPCNode): 120 121 "An XML-RPC element." 122 123 pass 124 125 class XMLRPCDocument(libxml2dom._Document, XMLRPCNode): 126 127 "An XML-RPC document fragment." 128 129 def _method(self): 130 return (self.xpath("methodCall|methodResponse") or [None])[0] 131 132 def _fault(self): 133 if self.method is not None: 134 return self.method.fault 135 else: 136 return None 137 138 method = property(_method) 139 fault = property(_fault) 140 141 # Node construction methods. 142 143 def createMethodCall(self): 144 return self.ownerDocument.createElement("methodCall") 145 146 def createMethodResponse(self): 147 return self.ownerDocument.createElement("methodResponse") 148 149 class XMLRPCMethodElement(XMLRPCNode): 150 151 "An XML-RPC method element." 152 153 def _fault(self): 154 return (self.xpath("./fault") or [None])[0] 155 156 def _methodNameElement(self): 157 return (self.xpath("./methodName") or [None])[0] 158 159 def _methodName(self): 160 name = self.methodNameElement 161 if name is not None: 162 return name.value 163 else: 164 return None 165 166 def _setMethodName(self, name): 167 if self.methodNameElement is None: 168 methodName = self.createMethodName() 169 self.appendChild(methodName) 170 self.methodNameElement.value = name 171 172 def _parameters(self): 173 return self.xpath("./params/param") 174 175 def _parameterValues(self): 176 values = self.xpath("./params/param/value") 177 if values: 178 items = [] 179 for value in values: 180 items.append(self._get_value(value)) 181 return items 182 else: 183 return [] 184 185 def _setParameterValues(self, parameters): 186 param_list = self.parameters 187 params = (self.xpath("./params") or [None])[0] 188 189 # Remove any previous parameters. 190 191 if params: 192 if param_list: 193 for param in param_list: 194 params.removeChild(param) 195 else: 196 params = self.createParameters() 197 self.appendChild(params) 198 199 # Add parameter values. 200 201 for parameter in parameters: 202 param = self.ownerDocument.createElement("param") 203 params.appendChild(param) 204 value = self.ownerDocument.createElement("value") 205 param.appendChild(value) 206 self._add_value(value, parameter) 207 208 # Internal methods. 209 210 def _add_value(self, value, parameter): 211 212 "Add to the 'value' element the given 'parameter'." 213 214 if parameter.name.ns == "struct": 215 if isinstance(parameter.value, dict): 216 items = parameter.value.items() 217 else: 218 items = parameter.value 219 220 # Create a struct element and add the members. 221 222 struct = self.ownerDocument.createElement("struct") 223 value.appendChild(struct) 224 225 for item in items: 226 member = struct.createMember() 227 struct.appendChild(member) 228 229 # Peek into the item to set up the name. 230 231 member.memberName = item.name 232 233 # Add the item inside a new value element. 234 235 memberValue = member.createValue() 236 member.appendChild(memberValue) 237 self._add_value(memberValue, item) 238 239 elif parameter.name.ns == "array": 240 241 # Create an array element and add the members. 242 243 array = self.ownerDocument.createElement("array") 244 value.appendChild(array) 245 data = array.createData() 246 array.appendChild(data) 247 248 for item in parameter.value: 249 250 # Add the item inside a new value element. 251 252 data_value = data.createValue() 253 data.appendChild(data_value) 254 self._add_value(data_value, item) 255 256 else: 257 container = self.ownerDocument.createElement(parameter.name.ns) 258 value.appendChild(container) 259 container.value = unicode(parameter.value) 260 261 def _get_value(self, value, name=None): 262 263 """ 264 Return the parameter name and value from within the given 'value' 265 element, using the optional 'name' as part of the returned name if 266 specified. 267 """ 268 269 if value.type == "struct": 270 items = [] 271 272 # Peek inside member values to get member names. 273 274 for member in value.container.members: 275 items.append(self._get_value(member.value, member.memberName)) 276 return ParameterValue(ParameterName(value.type, name), items) 277 278 elif value.type == "array": 279 items = [] 280 for data_value in value.container.data.values: 281 items.append(self._get_value(data_value)) 282 return ParameterValue(ParameterName(value.type, name), items) 283 284 else: 285 return ParameterValue(ParameterName(value.type, name), value.container.value) 286 287 # Node construction methods. 288 289 def createMethodName(self): 290 return self.ownerDocument.createElement("methodName") 291 292 def createParameters(self): 293 return self.ownerDocument.createElement("params") 294 295 def createFault(self): 296 return self.ownerDocument.createElement("fault") 297 298 fault = property(_fault) 299 methodNameElement = property(_methodNameElement) 300 methodName = property(_methodName, _setMethodName) 301 parameters = property(_parameters) 302 parameterValues = property(_parameterValues, _setParameterValues) 303 304 class XMLRPCArrayElement(XMLRPCNode): 305 306 "An XML-RPC array element." 307 308 def _data(self): 309 return (self.xpath("./data") or [None])[0] 310 311 # Node construction methods. 312 313 def createData(self): 314 return self.ownerDocument.createElement("data") 315 316 data = property(_data) 317 318 class XMLRPCStructElement(XMLRPCNode): 319 320 "An XML-RPC structure element." 321 322 def _members(self): 323 return self.xpath("./member") 324 325 # Node construction methods. 326 327 def createMember(self): 328 return self.ownerDocument.createElement("member") 329 330 members = property(_members) 331 332 class XMLRPCDataElement(XMLRPCNode): 333 334 "An XML-RPC array data element." 335 336 def _values(self): 337 return self.xpath("./value") 338 339 values = property(_values) 340 341 # Node construction methods. 342 343 def createValue(self): 344 return self.ownerDocument.createElement("value") 345 346 class XMLRPCMemberElement(XMLRPCNode): 347 348 "An XML-RPC structure member element." 349 350 def _value(self): 351 return (self.xpath("./value") or [None])[0] 352 353 def _nameElement(self): 354 return (self.xpath("./name") or [None])[0] 355 356 def _memberName(self): 357 if self.nameElement is not None: 358 return self.nameElement.value 359 else: 360 return None 361 362 def _setMemberName(self, name): 363 if self.nameElement is None: 364 nameElement = self.createName() 365 self.appendChild(nameElement) 366 self.nameElement.value = name 367 368 # Node construction methods. 369 370 def createName(self): 371 return self.ownerDocument.createElement("name") 372 373 def createValue(self): 374 return self.ownerDocument.createElement("value") 375 376 value = property(_value) 377 nameElement = property(_nameElement) 378 memberName = property(_memberName, _setMemberName) 379 380 class XMLRPCStringElement(XMLRPCNode): 381 382 "An XML-RPC string element." 383 384 def _value(self): 385 return self.textContent.strip() 386 387 def _setValue(self, value): 388 for node in self.childNodes: 389 self.removeChild(node) 390 text = self.ownerDocument.createTextNode(value) 391 self.appendChild(text) 392 393 value = property(_value, _setValue) 394 395 class XMLRPCNameElement(XMLRPCStringElement): 396 397 "An XML-RPC name element." 398 399 pass 400 401 class XMLRPCValueElement(XMLRPCStringElement): 402 403 "An XML-RPC value element." 404 405 def _type(self): 406 elements = self.xpath("*") 407 if elements: 408 return elements[0].localName 409 else: 410 return "string" 411 412 def _container(self): 413 return (self.xpath("*") or [self])[0] 414 415 type = property(_type) 416 container = property(_container) 417 418 class XMLRPCMethodNameElement(XMLRPCStringElement): 419 420 "An XML-RPC method element." 421 422 pass 423 424 class XMLRPCIntegerElement(XMLRPCStringElement): 425 426 "An XML-RPC integer element." 427 428 pass 429 430 class XMLRPCBooleanElement(XMLRPCStringElement): 431 432 "An XML-RPC boolean element." 433 434 pass 435 436 class XMLRPCDoubleElement(XMLRPCStringElement): 437 438 "An XML-RPC double floating point number element." 439 440 pass 441 442 class XMLRPCDateTimeElement(XMLRPCStringElement): 443 444 "An XML-RPC date/time element." 445 446 pass 447 448 class XMLRPCBase64Element(XMLRPCStringElement): 449 450 "An XML-RPC integer element." 451 452 pass 453 454 class XMLRPCFaultElement(XMLRPCNode): 455 456 "An XML-RPC fault element." 457 458 def _code(self): 459 code = self.xpath("./value/struct/member[./name/text() = 'faultCode']/value/int") 460 if code: 461 return code[0].value 462 else: 463 return None 464 465 def _reason(self): 466 reason = self.xpath("./value/struct/member[./name/text() = 'faultString']/value/string") 467 if reason: 468 return reason[0].value 469 else: 470 return None 471 472 code = property(_code) 473 reason = property(_reason) 474 475 # Utility functions. 476 477 createDocument = libxml2dom.createDocument 478 createDocumentType = libxml2dom.createDocumentType 479 480 def createXMLRPCMessage(namespaceURI, localName): 481 return default_impl.createXMLRPCMessage(None, localName) 482 483 def createMethodCall(): 484 return default_impl.createMethodCall() 485 486 def createMethodResponse(): 487 return default_impl.createMethodResponse() 488 489 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 490 return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 491 492 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 493 return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 494 495 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 496 return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 497 498 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 499 return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 500 501 # Single instance of the implementation. 502 503 default_impl = XMLRPCImplementation() 504 505 # vim: tabstop=4 expandtab shiftwidth=4