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, 2008 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/test_xmlrpc_parse.py for more details. 29 30 Useful properties: 31 32 * container - returns the actual value element containing transmitted data 33 * contents - returns the converted data from within a container or a 34 container object 35 """ 36 37 import libxml2dom 38 from libxml2dom.macrolib import * 39 from libxml2dom.macrolib import \ 40 createDocument as Node_createDocument 41 import datetime 42 import xml.dom 43 from libxml2dom.values import ContentValue, SequenceValue 44 45 # Node classes. 46 47 class XMLRPCNode(libxml2dom.Node): 48 49 "Convenience modifications to nodes specific to libxml2dom.xmlrpc." 50 51 def add_or_replace_element(self, new_element): 52 53 """ 54 Add or replace the given 'new_element', using its localName to find any 55 element to be replaced. 56 """ 57 58 elements = self.xpath(new_element.localName) 59 if elements: 60 self.replaceChild(new_element, elements[0]) 61 else: 62 self.appendChild(new_element) 63 64 def serialise_value(self, parent, value): 65 66 "Serialise, under the 'parent', the given 'value' object." 67 68 if isinstance(value, (str, int, float, bool)): 69 valueElement = parent.createValue(typenames[value.__class__.__name__], 1) 70 valueElement.container.value = str(value) 71 72 elif isinstance(value, datetime.datetime): 73 valueElement = parent.createValue("dateTime.iso8601", 1) 74 valueElement.container.value = value.strftime("%Y%m%dT%H:%M:%S") 75 76 elif isinstance(value, (tuple, list)): 77 array = parent.createValue("array", 1).container 78 dataElement = array.createData(1) 79 for v in value: 80 self.serialise_value(dataElement, v) 81 82 elif isinstance(value, dict): 83 struct = parent.createValue("struct", 1).container 84 for k, v in value.items(): 85 member = struct.createMember(1) 86 member.memberName = str(k) 87 self.serialise_value(member, v) 88 89 else: 90 raise ValueError, "Value %r cannot be serialised." % value 91 92 class XMLRPCElement(XMLRPCNode): 93 94 "An XML-RPC element." 95 96 pass 97 98 class XMLRPCDocument(libxml2dom._Document, XMLRPCNode): 99 100 "An XML-RPC document fragment." 101 102 def _method(self): 103 return (self.xpath("methodCall|methodResponse") or [None])[0] 104 105 def _fault(self): 106 if self.method is not None: 107 return self.method.fault 108 else: 109 return None 110 111 method = property(_method) 112 fault = property(_fault) 113 114 # Node construction methods. 115 116 def createMethodCall(self, insert=0): 117 e = self.ownerDocument.createElement("methodCall") 118 if insert: 119 self.add_or_replace_element(e) 120 return e 121 122 def createMethodResponse(self, insert=0): 123 e = self.ownerDocument.createElement("methodResponse") 124 if insert: 125 self.add_or_replace_element(e) 126 return e 127 128 class XMLRPCMethodElement(XMLRPCNode): 129 130 "An XML-RPC method element." 131 132 def _fault(self): 133 return (self.xpath("./fault") or [None])[0] 134 135 def _params(self): 136 return (self.xpath("./params") or [None])[0] 137 138 def _methodNameElement(self): 139 return (self.xpath("./methodName") or [None])[0] 140 141 def _methodName(self): 142 name = self.methodNameElement 143 if name is not None: 144 return name.value 145 else: 146 return None 147 148 def _setMethodName(self, name): 149 if self.methodNameElement is None: 150 methodName = self.createMethodName() 151 self.appendChild(methodName) 152 self.methodNameElement.value = name 153 154 def _parameterValues(self): 155 if self.params: 156 return self.params.values() 157 else: 158 return None 159 160 def _setParameterValues(self, values): 161 params = self.createParameters(1) 162 for value in values: 163 param = params.createParameter(1) 164 self.serialise_value(param, value) 165 166 def _parameters(self): 167 168 "Return a list of the individual param elements." 169 170 return self.xpath("./params/param") 171 172 # Node construction methods. 173 174 def createMethodName(self, insert=0): 175 e = self.ownerDocument.createElement("methodName") 176 if insert: 177 self.add_or_replace_element(e) 178 return e 179 180 def createParameters(self, insert=0): 181 e = self.ownerDocument.createElement("params") 182 if insert: 183 self.add_or_replace_element(e) 184 return e 185 186 def createFault(self, insert=0): 187 e = self.ownerDocument.createElement("fault") 188 if insert: 189 self.add_or_replace_element(e) 190 return e 191 192 fault = property(_fault) 193 params = property(_params) 194 methodNameElement = property(_methodNameElement) 195 methodName = property(_methodName, _setMethodName) 196 parameterValues = property(_parameterValues, _setParameterValues) 197 parameters = property(_parameters) 198 199 class XMLRPCParametersElement(SequenceValue, XMLRPCNode): 200 201 """ 202 An XML-RPC parameters/params element. 203 204 This element behaves like a list in that values can be appended to it and 205 these will be added as new parameters. 206 """ 207 208 def _parameters(self): 209 210 "Return a list of the individual param elements." 211 212 return self.xpath("./param") 213 214 # Sequence emulation. 215 216 def append(self, value): 217 param = self.createParameter(1) 218 self.serialise_value(param, value) 219 220 def values(self): 221 return [value.contents for value in self.xpath("./param/value")] 222 223 def __repr__(self): 224 return "<XMLRPCParametersElement: %r>" % self.parameters 225 226 # Node construction methods. 227 228 def createParameter(self, insert=0): 229 e = self.ownerDocument.createElement("param") 230 if insert: 231 self.appendChild(e) 232 return e 233 234 parameters = property(_parameters) 235 236 class XMLRPCParameterElement(ContentValue, XMLRPCNode): 237 238 """ 239 An XML-RPC parameter/param element. 240 241 This element behaves like a list in that values can be appended to it and 242 these will be added either as new elements in an existing array or struct, 243 or as new elements in a new array. 244 """ 245 246 def _valueElement(self): 247 return (self.xpath("./value") or [None])[0] 248 249 def _contents(self): 250 if self.valueElement is not None: 251 return self.valueElement.contents 252 else: 253 return self 254 255 # Sequence emulation. 256 257 def __len__(self): 258 return len(self.values()) 259 260 def __getitem__(self, i): 261 return self.values()[i] 262 263 def append(self, value): 264 if self.valueElement is None: 265 self.createValue("array", 1) 266 self.contents.append(value) 267 268 def values(self): 269 if self.contents is not self: 270 return self.contents.values() 271 else: 272 return [] 273 274 def __repr__(self): 275 if self.contents is self: 276 return "<XMLRPCParameterElement>" 277 else: 278 return "<XMLRPCParameterElement: %r>" % self.contents 279 280 # Node construction methods. 281 282 def createValue(self, typename=None, insert=0): 283 value = self.ownerDocument.createElement("value") 284 if typename is not None: 285 contents = self.ownerDocument.createElement(typename) 286 value.appendChild(contents) 287 if insert: 288 self.add_or_replace_element(value) 289 return value 290 291 valueElement = property(_valueElement) 292 contents = property(_contents) 293 294 class XMLRPCArrayElement(SequenceValue, XMLRPCNode): 295 296 """ 297 An XML-RPC array element. 298 299 This element behaves like a list in that values can be appended to it and 300 these will be added as new elements in the array. 301 """ 302 303 def _dataElement(self): 304 return (self.xpath("./data") or [None])[0] 305 306 def _contents(self): 307 return self 308 309 # Sequence emulation. 310 311 def append(self, value): 312 if self.dataElement is None: 313 self.createData(1) 314 self.serialise_value(self.dataElement, value) 315 316 def values(self): 317 return [v.contents for v in self.xpath("./data/value")] 318 319 def __repr__(self): 320 return "<XMLRPCArrayElement: %r>" % self.values() 321 322 # Node construction methods. 323 324 def createData(self, insert=0): 325 e = self.ownerDocument.createElement("data") 326 if insert: 327 self.add_or_replace_element(e) 328 return e 329 330 dataElement = property(_dataElement) 331 contents = property(_contents) 332 333 class XMLRPCStructElement(SequenceValue, XMLRPCNode): 334 335 """ 336 An XML-RPC structure element. 337 338 This element behaves like a list in that values can be appended to it and 339 these will be added as new (name, value) members in the structure. 340 """ 341 342 def _members(self): 343 return self.xpath("./member") 344 345 def _contents(self): 346 return self 347 348 # Sequence emulation. 349 350 def __len__(self): 351 return len(self.members) 352 353 def __getitem__(self, i): 354 return self.members[i] 355 356 def append(self, item): 357 name, value = item 358 member = self.createMember(1) 359 member.memberName = name 360 self.serialise_value(member, value) 361 362 def __repr__(self): 363 return "<XMLRPCStructElement: %r>" % self.items() 364 365 # Dictionary emulation. 366 367 def keys(self): 368 return [member.memberName for member in self.members] 369 370 def values(self): 371 return [member.memberValue for member in self.members] 372 373 def items(self): 374 return [(member.memberName, member.memberValue) for member in self.members] 375 376 # Node construction methods. 377 378 def createMember(self, insert=0): 379 e = self.ownerDocument.createElement("member") 380 if insert: 381 self.appendChild(e) 382 return e 383 384 members = property(_members) 385 contents = property(_contents) 386 387 class XMLRPCDataElement(SequenceValue, XMLRPCNode): 388 389 """ 390 An XML-RPC array data element. 391 392 This element behaves like a list in that values can be appended to it and 393 these will be added as new elements in the array. 394 """ 395 396 def _contents(self): 397 return self 398 399 # Sequence emulation. 400 401 def append(self, value): 402 self.serialise_value(self, value) 403 404 def values(self): 405 return [v.contents for v in self.xpath("./value")] 406 407 # Node construction methods. 408 409 def createValue(self, typename=None, insert=0): 410 value = self.ownerDocument.createElement("value") 411 if typename is not None: 412 contents = self.ownerDocument.createElement(typename) 413 value.appendChild(contents) 414 if insert: 415 self.appendChild(value) 416 return value 417 418 contents = property(_contents) 419 420 class XMLRPCMemberElement(XMLRPCNode): 421 422 """ 423 An XML-RPC structure member element. 424 425 This element behaves like a tuple of the form (name, value). 426 """ 427 428 def _valueElement(self): 429 return (self.xpath("./value") or [None])[0] 430 431 def _nameElement(self): 432 return (self.xpath("./name") or [None])[0] 433 434 def _memberName(self): 435 if self.nameElement is not None: 436 return self.nameElement.value 437 else: 438 return None 439 440 def _setMemberName(self, name): 441 if self.nameElement is None: 442 nameElement = self.createName() 443 self.appendChild(nameElement) 444 self.nameElement.value = name 445 446 def _memberValue(self): 447 if self.valueElement is None: 448 return None 449 else: 450 return self.valueElement.contents 451 452 def _contents(self): 453 return self 454 455 # Item (name, value) emulation. 456 457 def __len__(self): 458 return 2 459 460 def __getitem__(self, i): 461 return (self.memberName, self.valueElement.contents)[i] 462 463 # Equality testing. 464 465 def __eq__(self, other): 466 return self[0] == other[0] and self[1] == other[1] 467 468 def __ne__(self, other): 469 return not self.__eq__(other) 470 471 # Node construction methods. 472 473 def createName(self, insert=0): 474 e = self.ownerDocument.createElement("name") 475 if insert: 476 self.add_or_replace_element(e) 477 return e 478 479 def createValue(self, typename=None, insert=0): 480 value = self.ownerDocument.createElement("value") 481 if typename is not None: 482 contents = self.ownerDocument.createElement(typename) 483 value.appendChild(contents) 484 if insert: 485 self.add_or_replace_element(value) 486 return value 487 488 nameElement = property(_nameElement) 489 memberName = property(_memberName, _setMemberName) 490 memberValue = property(_memberValue) 491 valueElement = property(_valueElement) 492 contents = property(_contents) 493 494 class XMLRPCStringElement(ContentValue, XMLRPCNode): 495 496 "An XML-RPC string element." 497 498 typename = "string" 499 500 def _value(self): 501 return self.textContent.strip() 502 503 def _setValue(self, value): 504 for node in self.childNodes: 505 self.removeChild(node) 506 text = self.ownerDocument.createTextNode(value) 507 self.appendChild(text) 508 509 def _contents(self): 510 return convert(self.typename, self.value) 511 512 def __repr__(self): 513 return "<%s: %r>" % (self.__class__.__name__, self.contents) 514 515 value = property(_value, _setValue) 516 contents = property(_contents) 517 518 class XMLRPCNameElement(XMLRPCStringElement): 519 520 "An XML-RPC name element." 521 522 pass 523 524 class XMLRPCMethodNameElement(XMLRPCNameElement): 525 526 "An XML-RPC method element." 527 528 pass 529 530 class XMLRPCValueElement(XMLRPCStringElement): 531 532 "An XML-RPC value element." 533 534 def _value(self): 535 if self.container is self: 536 return XMLRPCStringElement._value(self) 537 else: 538 return None 539 540 def _setValue(self, value): 541 if self.container is self: 542 return XMLRPCStringElement._setValue(self, value) 543 else: 544 raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR) 545 546 def _contents(self): 547 if self.container is self: 548 return XMLRPCStringElement._contents(self) 549 else: 550 return self.container.contents 551 552 def _type(self): 553 if self.container is self: 554 return "string" 555 else: 556 return self.container.localName 557 558 def _setType(self, typename): 559 new_contents = self.ownerDocument.createElement(typename) 560 self.add_or_replace_element(new_contents) 561 562 def _container(self): 563 return (self.xpath("*") or [self])[0] 564 565 value = property(_value, _setValue) 566 type = property(_type, _setType) 567 container = property(_container) 568 contents = property(_contents) 569 570 class XMLRPCIntegerElement(XMLRPCStringElement): 571 572 "An XML-RPC integer element." 573 574 typename = "int" 575 576 class XMLRPCBooleanElement(XMLRPCStringElement): 577 578 "An XML-RPC boolean element." 579 580 typename = "boolean" 581 582 class XMLRPCDoubleElement(XMLRPCStringElement): 583 584 "An XML-RPC double floating point number element." 585 586 typename = "double" 587 588 class XMLRPCDateTimeElement(XMLRPCStringElement): 589 590 "An XML-RPC date/time element." 591 592 typename = "datetime" 593 594 class XMLRPCBase64Element(XMLRPCStringElement): 595 596 "An XML-RPC integer element." 597 598 typename = "base64" 599 600 class XMLRPCFaultElement(XMLRPCNode): 601 602 "An XML-RPC fault element." 603 604 def _code(self): 605 code = self.xpath("./value/struct/member[./name/text() = 'faultCode']/value/int") 606 if code: 607 return code[0].value 608 else: 609 return None 610 611 def _reason(self): 612 reason = self.xpath("./value/struct/member[./name/text() = 'faultString']/value/string") 613 if reason: 614 return reason[0].value 615 else: 616 return None 617 618 code = property(_code) 619 reason = property(_reason) 620 621 # Conversion functions. 622 623 def convert(typename, value): 624 return default_converters[typename](value) 625 626 def boolean(s): 627 if s.lower() == "true": 628 return True 629 elif s.lower() == "false": 630 return False 631 else: 632 raise ValueError, "String value %r not convertable to boolean." % s 633 634 def iso8601(s): 635 year, month, day, hour, minute, second = map(int, (s[:4], s[4:6], s[6:8], s[9:11], s[12:14], s[15:17])) 636 return datetime.datetime(year, month, day, hour, minute, second) 637 638 default_converters = { 639 "string" : unicode, 640 "int" : int, 641 "i4" : int, 642 "double" : float, 643 "boolean" : boolean, 644 "dateTime.iso8601" : iso8601, 645 "base64" : str 646 } 647 648 typenames = { 649 "str" : "string", 650 "int" : "int", 651 "bool" : "boolean", 652 "float" : "double" 653 } 654 655 # Implementation-related functionality. 656 657 class XMLRPCImplementation(libxml2dom.Implementation): 658 659 "Contains an XML-RPC-specific implementation." 660 661 # Mapping of element names to wrappers. 662 663 _class_for_name = { 664 "methodCall" : XMLRPCMethodElement, 665 "methodResponse" : XMLRPCMethodElement, 666 "methodName" : XMLRPCMethodNameElement, 667 "params" : XMLRPCParametersElement, 668 "param" : XMLRPCParameterElement, 669 "fault" : XMLRPCFaultElement, 670 "string" : XMLRPCStringElement, 671 "int" : XMLRPCIntegerElement, 672 "i4" : XMLRPCIntegerElement, 673 "boolean" : XMLRPCBooleanElement, 674 "double" : XMLRPCDoubleElement, 675 "dateTime.iso8601" : XMLRPCDateTimeElement, 676 "base64" : XMLRPCBase64Element, 677 "struct" : XMLRPCStructElement, 678 "member" : XMLRPCMemberElement, 679 "value" : XMLRPCValueElement, 680 "name" : XMLRPCNameElement, 681 "array" : XMLRPCArrayElement, 682 "data" : XMLRPCDataElement 683 } 684 685 # Wrapping of documents. 686 687 def adoptDocument(self, node): 688 return XMLRPCDocument(node, self) 689 690 # Factory functions. 691 692 def get_node(self, _node, context_node): 693 694 """ 695 Get a libxml2dom node for the given low-level '_node' and libxml2dom 696 'context_node'. 697 """ 698 699 if Node_nodeType(_node) == context_node.ELEMENT_NODE: 700 701 # Make special objects for certain elements. 702 # Otherwise, make generic XML-RPC elements. 703 704 cls = self._class_for_name.get(Node_localName(_node)) or XMLRPCElement 705 return cls(_node, self, context_node.ownerDocument) 706 707 else: 708 return libxml2dom.Implementation.get_node(self, _node, context_node) 709 710 # Convenience functions. 711 712 def createXMLRPCMessage(self, namespaceURI, localName): 713 714 "Create a new XML-RPC message document (fragment)." 715 716 return XMLRPCDocument(Node_createDocument(namespaceURI, localName, None), self).documentElement 717 718 def createMethodCall(self, name=None): 719 720 """ 721 Create and return a message fragment for a method call having the given 722 'name'. 723 """ 724 725 message = self.createXMLRPCMessage(None, "methodCall") 726 if name is not None: 727 message.methodName = name 728 return message 729 730 def createMethodResponse(self): 731 732 "Create and return a message fragment for a method response." 733 734 return self.createXMLRPCMessage(None, "methodResponse") 735 736 # Utility functions. 737 738 createDocument = libxml2dom.createDocument 739 createDocumentType = libxml2dom.createDocumentType 740 741 def createXMLRPCMessage(namespaceURI, localName): 742 return default_impl.createXMLRPCMessage(None, localName) 743 744 def createMethodCall(name=None): 745 return default_impl.createMethodCall(name) 746 747 def createMethodResponse(): 748 return default_impl.createMethodResponse() 749 750 def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): 751 return libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 752 753 def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): 754 return libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 755 756 def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): 757 return libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 758 759 def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): 760 return libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) 761 762 # Single instance of the implementation. 763 764 default_impl = XMLRPCImplementation() 765 766 # vim: tabstop=4 expandtab shiftwidth=4