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