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