1# 2# XML-RPC CLIENT LIBRARY 3# $Id$ 4# 5# an XML-RPC client interface for Python. 6# 7# the marshalling and response parser code can also be used to 8# implement XML-RPC servers. 9# 10# Notes: 11# this version is designed to work with Python 2.1 or newer. 12# 13# History: 14# 1999-01-14 fl Created 15# 1999-01-15 fl Changed dateTime to use localtime 16# 1999-01-16 fl Added Binary/base64 element, default to RPC2 service 17# 1999-01-19 fl Fixed array data element (from Skip Montanaro) 18# 1999-01-21 fl Fixed dateTime constructor, etc. 19# 1999-02-02 fl Added fault handling, handle empty sequences, etc. 20# 1999-02-10 fl Fixed problem with empty responses (from Skip Montanaro) 21# 1999-06-20 fl Speed improvements, pluggable parsers/transports (0.9.8) 22# 2000-11-28 fl Changed boolean to check the truth value of its argument 23# 2001-02-24 fl Added encoding/Unicode/SafeTransport patches 24# 2001-02-26 fl Added compare support to wrappers (0.9.9/1.0b1) 25# 2001-03-28 fl Make sure response tuple is a singleton 26# 2001-03-29 fl Don't require empty params element (from Nicholas Riley) 27# 2001-06-10 fl Folded in _xmlrpclib accelerator support (1.0b2) 28# 2001-08-20 fl Base xmlrpclib.Error on built-in Exception (from Paul Prescod) 29# 2001-09-03 fl Allow Transport subclass to override getparser 30# 2001-09-10 fl Lazy import of urllib, cgi, xmllib (20x import speedup) 31# 2001-10-01 fl Remove containers from memo cache when done with them 32# 2001-10-01 fl Use faster escape method (80% dumps speedup) 33# 2001-10-02 fl More dumps microtuning 34# 2001-10-04 fl Make sure import expat gets a parser (from Guido van Rossum) 35# 2001-10-10 sm Allow long ints to be passed as ints if they don't overflow 36# 2001-10-17 sm Test for int and long overflow (allows use on 64-bit systems) 37# 2001-11-12 fl Use repr() to marshal doubles (from Paul Felix) 38# 2002-03-17 fl Avoid buffered read when possible (from James Rucker) 39# 2002-04-07 fl Added pythondoc comments 40# 2002-04-16 fl Added __str__ methods to datetime/binary wrappers 41# 2002-05-15 fl Added error constants (from Andrew Kuchling) 42# 2002-06-27 fl Merged with Python CVS version 43# 2002-10-22 fl Added basic authentication (based on code from Phillip Eby) 44# 2003-01-22 sm Add support for the bool type 45# 2003-02-27 gvr Remove apply calls 46# 2003-04-24 sm Use cStringIO if available 47# 2003-04-25 ak Add support for nil 48# 2003-06-15 gn Add support for time.struct_time 49# 2003-07-12 gp Correct marshalling of Faults 50# 2003-10-31 mvl Add multicall support 51# 2004-08-20 mvl Bump minimum supported Python version to 2.1 52# 53# Copyright (c) 1999-2002 by Secret Labs AB. 54# Copyright (c) 1999-2002 by Fredrik Lundh. 55# 56# info@pythonware.com 57# http://www.pythonware.com 58# 59# -------------------------------------------------------------------- 60# The XML-RPC client interface is 61# 62# Copyright (c) 1999-2002 by Secret Labs AB 63# Copyright (c) 1999-2002 by Fredrik Lundh 64# 65# By obtaining, using, and/or copying this software and/or its 66# associated documentation, you agree that you have read, understood, 67# and will comply with the following terms and conditions: 68# 69# Permission to use, copy, modify, and distribute this software and 70# its associated documentation for any purpose and without fee is 71# hereby granted, provided that the above copyright notice appears in 72# all copies, and that both that copyright notice and this permission 73# notice appear in supporting documentation, and that the name of 74# Secret Labs AB or the author not be used in advertising or publicity 75# pertaining to distribution of the software without specific, written 76# prior permission. 77# 78# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD 79# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT- 80# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR 81# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 82# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, 83# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS 84# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 85# OF THIS SOFTWARE. 86# -------------------------------------------------------------------- 87 88""" 89Ported using Python-Future from the Python 3.3 standard library. 90 91An XML-RPC client interface for Python. 92 93The marshalling and response parser code can also be used to 94implement XML-RPC servers. 95 96Exported exceptions: 97 98 Error Base class for client errors 99 ProtocolError Indicates an HTTP protocol error 100 ResponseError Indicates a broken response package 101 Fault Indicates an XML-RPC fault package 102 103Exported classes: 104 105 ServerProxy Represents a logical connection to an XML-RPC server 106 107 MultiCall Executor of boxcared xmlrpc requests 108 DateTime dateTime wrapper for an ISO 8601 string or time tuple or 109 localtime integer value to generate a "dateTime.iso8601" 110 XML-RPC value 111 Binary binary data wrapper 112 113 Marshaller Generate an XML-RPC params chunk from a Python data structure 114 Unmarshaller Unmarshal an XML-RPC response from incoming XML event message 115 Transport Handles an HTTP transaction to an XML-RPC server 116 SafeTransport Handles an HTTPS transaction to an XML-RPC server 117 118Exported constants: 119 120 (none) 121 122Exported functions: 123 124 getparser Create instance of the fastest available parser & attach 125 to an unmarshalling object 126 dumps Convert an argument tuple or a Fault instance to an XML-RPC 127 request (or response, if the methodresponse option is used). 128 loads Convert an XML-RPC packet to unmarshalled data plus a method 129 name (None if not present). 130""" 131 132from __future__ import (absolute_import, division, print_function, 133 unicode_literals) 134from future.builtins import bytes, dict, int, range, str 135 136import base64 137# Py2.7 compatibility hack 138base64.encodebytes = base64.encodestring 139base64.decodebytes = base64.decodestring 140import sys 141import time 142from datetime import datetime 143from future.backports.http import client as http_client 144from future.backports.urllib import parse as urllib_parse 145from future.utils import ensure_new_type 146from xml.parsers import expat 147import socket 148import errno 149from io import BytesIO 150try: 151 import gzip 152except ImportError: 153 gzip = None #python can be built without zlib/gzip support 154 155# -------------------------------------------------------------------- 156# Internal stuff 157 158def escape(s): 159 s = s.replace("&", "&") 160 s = s.replace("<", "<") 161 return s.replace(">", ">",) 162 163# used in User-Agent header sent 164__version__ = sys.version[:3] 165 166# xmlrpc integer limits 167MAXINT = 2**31-1 168MININT = -2**31 169 170# -------------------------------------------------------------------- 171# Error constants (from Dan Libby's specification at 172# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) 173 174# Ranges of errors 175PARSE_ERROR = -32700 176SERVER_ERROR = -32600 177APPLICATION_ERROR = -32500 178SYSTEM_ERROR = -32400 179TRANSPORT_ERROR = -32300 180 181# Specific errors 182NOT_WELLFORMED_ERROR = -32700 183UNSUPPORTED_ENCODING = -32701 184INVALID_ENCODING_CHAR = -32702 185INVALID_XMLRPC = -32600 186METHOD_NOT_FOUND = -32601 187INVALID_METHOD_PARAMS = -32602 188INTERNAL_ERROR = -32603 189 190# -------------------------------------------------------------------- 191# Exceptions 192 193## 194# Base class for all kinds of client-side errors. 195 196class Error(Exception): 197 """Base class for client errors.""" 198 def __str__(self): 199 return repr(self) 200 201## 202# Indicates an HTTP-level protocol error. This is raised by the HTTP 203# transport layer, if the server returns an error code other than 200 204# (OK). 205# 206# @param url The target URL. 207# @param errcode The HTTP error code. 208# @param errmsg The HTTP error message. 209# @param headers The HTTP header dictionary. 210 211class ProtocolError(Error): 212 """Indicates an HTTP protocol error.""" 213 def __init__(self, url, errcode, errmsg, headers): 214 Error.__init__(self) 215 self.url = url 216 self.errcode = errcode 217 self.errmsg = errmsg 218 self.headers = headers 219 def __repr__(self): 220 return ( 221 "<ProtocolError for %s: %s %s>" % 222 (self.url, self.errcode, self.errmsg) 223 ) 224 225## 226# Indicates a broken XML-RPC response package. This exception is 227# raised by the unmarshalling layer, if the XML-RPC response is 228# malformed. 229 230class ResponseError(Error): 231 """Indicates a broken response package.""" 232 pass 233 234## 235# Indicates an XML-RPC fault response package. This exception is 236# raised by the unmarshalling layer, if the XML-RPC response contains 237# a fault string. This exception can also be used as a class, to 238# generate a fault XML-RPC message. 239# 240# @param faultCode The XML-RPC fault code. 241# @param faultString The XML-RPC fault string. 242 243class Fault(Error): 244 """Indicates an XML-RPC fault package.""" 245 def __init__(self, faultCode, faultString, **extra): 246 Error.__init__(self) 247 self.faultCode = faultCode 248 self.faultString = faultString 249 def __repr__(self): 250 return "<Fault %s: %r>" % (ensure_new_type(self.faultCode), 251 ensure_new_type(self.faultString)) 252 253# -------------------------------------------------------------------- 254# Special values 255 256## 257# Backwards compatibility 258 259boolean = Boolean = bool 260 261## 262# Wrapper for XML-RPC DateTime values. This converts a time value to 263# the format used by XML-RPC. 264# <p> 265# The value can be given as a datetime object, as a string in the 266# format "yyyymmddThh:mm:ss", as a 9-item time tuple (as returned by 267# time.localtime()), or an integer value (as returned by time.time()). 268# The wrapper uses time.localtime() to convert an integer to a time 269# tuple. 270# 271# @param value The time, given as a datetime object, an ISO 8601 string, 272# a time tuple, or an integer time value. 273 274 275### For Python-Future: 276def _iso8601_format(value): 277 return "%04d%02d%02dT%02d:%02d:%02d" % ( 278 value.year, value.month, value.day, 279 value.hour, value.minute, value.second) 280### 281# Issue #13305: different format codes across platforms 282# _day0 = datetime(1, 1, 1) 283# if _day0.strftime('%Y') == '0001': # Mac OS X 284# def _iso8601_format(value): 285# return value.strftime("%Y%m%dT%H:%M:%S") 286# elif _day0.strftime('%4Y') == '0001': # Linux 287# def _iso8601_format(value): 288# return value.strftime("%4Y%m%dT%H:%M:%S") 289# else: 290# def _iso8601_format(value): 291# return value.strftime("%Y%m%dT%H:%M:%S").zfill(17) 292# del _day0 293 294 295def _strftime(value): 296 if isinstance(value, datetime): 297 return _iso8601_format(value) 298 299 if not isinstance(value, (tuple, time.struct_time)): 300 if value == 0: 301 value = time.time() 302 value = time.localtime(value) 303 304 return "%04d%02d%02dT%02d:%02d:%02d" % value[:6] 305 306class DateTime(object): 307 """DateTime wrapper for an ISO 8601 string or time tuple or 308 localtime integer value to generate 'dateTime.iso8601' XML-RPC 309 value. 310 """ 311 312 def __init__(self, value=0): 313 if isinstance(value, str): 314 self.value = value 315 else: 316 self.value = _strftime(value) 317 318 def make_comparable(self, other): 319 if isinstance(other, DateTime): 320 s = self.value 321 o = other.value 322 elif isinstance(other, datetime): 323 s = self.value 324 o = _iso8601_format(other) 325 elif isinstance(other, str): 326 s = self.value 327 o = other 328 elif hasattr(other, "timetuple"): 329 s = self.timetuple() 330 o = other.timetuple() 331 else: 332 otype = (hasattr(other, "__class__") 333 and other.__class__.__name__ 334 or type(other)) 335 raise TypeError("Can't compare %s and %s" % 336 (self.__class__.__name__, otype)) 337 return s, o 338 339 def __lt__(self, other): 340 s, o = self.make_comparable(other) 341 return s < o 342 343 def __le__(self, other): 344 s, o = self.make_comparable(other) 345 return s <= o 346 347 def __gt__(self, other): 348 s, o = self.make_comparable(other) 349 return s > o 350 351 def __ge__(self, other): 352 s, o = self.make_comparable(other) 353 return s >= o 354 355 def __eq__(self, other): 356 s, o = self.make_comparable(other) 357 return s == o 358 359 def __ne__(self, other): 360 s, o = self.make_comparable(other) 361 return s != o 362 363 def timetuple(self): 364 return time.strptime(self.value, "%Y%m%dT%H:%M:%S") 365 366 ## 367 # Get date/time value. 368 # 369 # @return Date/time value, as an ISO 8601 string. 370 371 def __str__(self): 372 return self.value 373 374 def __repr__(self): 375 return "<DateTime %r at %x>" % (ensure_new_type(self.value), id(self)) 376 377 def decode(self, data): 378 self.value = str(data).strip() 379 380 def encode(self, out): 381 out.write("<value><dateTime.iso8601>") 382 out.write(self.value) 383 out.write("</dateTime.iso8601></value>\n") 384 385def _datetime(data): 386 # decode xml element contents into a DateTime structure. 387 value = DateTime() 388 value.decode(data) 389 return value 390 391def _datetime_type(data): 392 return datetime.strptime(data, "%Y%m%dT%H:%M:%S") 393 394## 395# Wrapper for binary data. This can be used to transport any kind 396# of binary data over XML-RPC, using BASE64 encoding. 397# 398# @param data An 8-bit string containing arbitrary data. 399 400class Binary(object): 401 """Wrapper for binary data.""" 402 403 def __init__(self, data=None): 404 if data is None: 405 data = b"" 406 else: 407 if not isinstance(data, (bytes, bytearray)): 408 raise TypeError("expected bytes or bytearray, not %s" % 409 data.__class__.__name__) 410 data = bytes(data) # Make a copy of the bytes! 411 self.data = data 412 413 ## 414 # Get buffer contents. 415 # 416 # @return Buffer contents, as an 8-bit string. 417 418 def __str__(self): 419 return str(self.data, "latin-1") # XXX encoding?! 420 421 def __eq__(self, other): 422 if isinstance(other, Binary): 423 other = other.data 424 return self.data == other 425 426 def __ne__(self, other): 427 if isinstance(other, Binary): 428 other = other.data 429 return self.data != other 430 431 def decode(self, data): 432 self.data = base64.decodebytes(data) 433 434 def encode(self, out): 435 out.write("<value><base64>\n") 436 encoded = base64.encodebytes(self.data) 437 out.write(encoded.decode('ascii')) 438 out.write("</base64></value>\n") 439 440def _binary(data): 441 # decode xml element contents into a Binary structure 442 value = Binary() 443 value.decode(data) 444 return value 445 446WRAPPERS = (DateTime, Binary) 447 448# -------------------------------------------------------------------- 449# XML parsers 450 451class ExpatParser(object): 452 # fast expat parser for Python 2.0 and later. 453 def __init__(self, target): 454 self._parser = parser = expat.ParserCreate(None, None) 455 self._target = target 456 parser.StartElementHandler = target.start 457 parser.EndElementHandler = target.end 458 parser.CharacterDataHandler = target.data 459 encoding = None 460 target.xml(encoding, None) 461 462 def feed(self, data): 463 self._parser.Parse(data, 0) 464 465 def close(self): 466 self._parser.Parse("", 1) # end of data 467 del self._target, self._parser # get rid of circular references 468 469# -------------------------------------------------------------------- 470# XML-RPC marshalling and unmarshalling code 471 472## 473# XML-RPC marshaller. 474# 475# @param encoding Default encoding for 8-bit strings. The default 476# value is None (interpreted as UTF-8). 477# @see dumps 478 479class Marshaller(object): 480 """Generate an XML-RPC params chunk from a Python data structure. 481 482 Create a Marshaller instance for each set of parameters, and use 483 the "dumps" method to convert your data (represented as a tuple) 484 to an XML-RPC params chunk. To write a fault response, pass a 485 Fault instance instead. You may prefer to use the "dumps" module 486 function for this purpose. 487 """ 488 489 # by the way, if you don't understand what's going on in here, 490 # that's perfectly ok. 491 492 def __init__(self, encoding=None, allow_none=False): 493 self.memo = {} 494 self.data = None 495 self.encoding = encoding 496 self.allow_none = allow_none 497 498 dispatch = {} 499 500 def dumps(self, values): 501 out = [] 502 write = out.append 503 dump = self.__dump 504 if isinstance(values, Fault): 505 # fault instance 506 write("<fault>\n") 507 dump({'faultCode': values.faultCode, 508 'faultString': values.faultString}, 509 write) 510 write("</fault>\n") 511 else: 512 # parameter block 513 # FIXME: the xml-rpc specification allows us to leave out 514 # the entire <params> block if there are no parameters. 515 # however, changing this may break older code (including 516 # old versions of xmlrpclib.py), so this is better left as 517 # is for now. See @XMLRPC3 for more information. /F 518 write("<params>\n") 519 for v in values: 520 write("<param>\n") 521 dump(v, write) 522 write("</param>\n") 523 write("</params>\n") 524 result = "".join(out) 525 return str(result) 526 527 def __dump(self, value, write): 528 try: 529 f = self.dispatch[type(ensure_new_type(value))] 530 except KeyError: 531 # check if this object can be marshalled as a structure 532 if not hasattr(value, '__dict__'): 533 raise TypeError("cannot marshal %s objects" % type(value)) 534 # check if this class is a sub-class of a basic type, 535 # because we don't know how to marshal these types 536 # (e.g. a string sub-class) 537 for type_ in type(value).__mro__: 538 if type_ in self.dispatch.keys(): 539 raise TypeError("cannot marshal %s objects" % type(value)) 540 # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix 541 # for the p3yk merge, this should probably be fixed more neatly. 542 f = self.dispatch["_arbitrary_instance"] 543 f(self, value, write) 544 545 def dump_nil (self, value, write): 546 if not self.allow_none: 547 raise TypeError("cannot marshal None unless allow_none is enabled") 548 write("<value><nil/></value>") 549 dispatch[type(None)] = dump_nil 550 551 def dump_bool(self, value, write): 552 write("<value><boolean>") 553 write(value and "1" or "0") 554 write("</boolean></value>\n") 555 dispatch[bool] = dump_bool 556 557 def dump_long(self, value, write): 558 if value > MAXINT or value < MININT: 559 raise OverflowError("long int exceeds XML-RPC limits") 560 write("<value><int>") 561 write(str(int(value))) 562 write("</int></value>\n") 563 dispatch[int] = dump_long 564 565 # backward compatible 566 dump_int = dump_long 567 568 def dump_double(self, value, write): 569 write("<value><double>") 570 write(repr(ensure_new_type(value))) 571 write("</double></value>\n") 572 dispatch[float] = dump_double 573 574 def dump_unicode(self, value, write, escape=escape): 575 write("<value><string>") 576 write(escape(value)) 577 write("</string></value>\n") 578 dispatch[str] = dump_unicode 579 580 def dump_bytes(self, value, write): 581 write("<value><base64>\n") 582 encoded = base64.encodebytes(value) 583 write(encoded.decode('ascii')) 584 write("</base64></value>\n") 585 dispatch[bytes] = dump_bytes 586 dispatch[bytearray] = dump_bytes 587 588 def dump_array(self, value, write): 589 i = id(value) 590 if i in self.memo: 591 raise TypeError("cannot marshal recursive sequences") 592 self.memo[i] = None 593 dump = self.__dump 594 write("<value><array><data>\n") 595 for v in value: 596 dump(v, write) 597 write("</data></array></value>\n") 598 del self.memo[i] 599 dispatch[tuple] = dump_array 600 dispatch[list] = dump_array 601 602 def dump_struct(self, value, write, escape=escape): 603 i = id(value) 604 if i in self.memo: 605 raise TypeError("cannot marshal recursive dictionaries") 606 self.memo[i] = None 607 dump = self.__dump 608 write("<value><struct>\n") 609 for k, v in value.items(): 610 write("<member>\n") 611 if not isinstance(k, str): 612 raise TypeError("dictionary key must be string") 613 write("<name>%s</name>\n" % escape(k)) 614 dump(v, write) 615 write("</member>\n") 616 write("</struct></value>\n") 617 del self.memo[i] 618 dispatch[dict] = dump_struct 619 620 def dump_datetime(self, value, write): 621 write("<value><dateTime.iso8601>") 622 write(_strftime(value)) 623 write("</dateTime.iso8601></value>\n") 624 dispatch[datetime] = dump_datetime 625 626 def dump_instance(self, value, write): 627 # check for special wrappers 628 if value.__class__ in WRAPPERS: 629 self.write = write 630 value.encode(self) 631 del self.write 632 else: 633 # store instance attributes as a struct (really?) 634 self.dump_struct(value.__dict__, write) 635 dispatch[DateTime] = dump_instance 636 dispatch[Binary] = dump_instance 637 # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix 638 # for the p3yk merge, this should probably be fixed more neatly. 639 dispatch["_arbitrary_instance"] = dump_instance 640 641## 642# XML-RPC unmarshaller. 643# 644# @see loads 645 646class Unmarshaller(object): 647 """Unmarshal an XML-RPC response, based on incoming XML event 648 messages (start, data, end). Call close() to get the resulting 649 data structure. 650 651 Note that this reader is fairly tolerant, and gladly accepts bogus 652 XML-RPC data without complaining (but not bogus XML). 653 """ 654 655 # and again, if you don't understand what's going on in here, 656 # that's perfectly ok. 657 658 def __init__(self, use_datetime=False, use_builtin_types=False): 659 self._type = None 660 self._stack = [] 661 self._marks = [] 662 self._data = [] 663 self._methodname = None 664 self._encoding = "utf-8" 665 self.append = self._stack.append 666 self._use_datetime = use_builtin_types or use_datetime 667 self._use_bytes = use_builtin_types 668 669 def close(self): 670 # return response tuple and target method 671 if self._type is None or self._marks: 672 raise ResponseError() 673 if self._type == "fault": 674 raise Fault(**self._stack[0]) 675 return tuple(self._stack) 676 677 def getmethodname(self): 678 return self._methodname 679 680 # 681 # event handlers 682 683 def xml(self, encoding, standalone): 684 self._encoding = encoding 685 # FIXME: assert standalone == 1 ??? 686 687 def start(self, tag, attrs): 688 # prepare to handle this element 689 if tag == "array" or tag == "struct": 690 self._marks.append(len(self._stack)) 691 self._data = [] 692 self._value = (tag == "value") 693 694 def data(self, text): 695 self._data.append(text) 696 697 def end(self, tag): 698 # call the appropriate end tag handler 699 try: 700 f = self.dispatch[tag] 701 except KeyError: 702 pass # unknown tag ? 703 else: 704 return f(self, "".join(self._data)) 705 706 # 707 # accelerator support 708 709 def end_dispatch(self, tag, data): 710 # dispatch data 711 try: 712 f = self.dispatch[tag] 713 except KeyError: 714 pass # unknown tag ? 715 else: 716 return f(self, data) 717 718 # 719 # element decoders 720 721 dispatch = {} 722 723 def end_nil (self, data): 724 self.append(None) 725 self._value = 0 726 dispatch["nil"] = end_nil 727 728 def end_boolean(self, data): 729 if data == "0": 730 self.append(False) 731 elif data == "1": 732 self.append(True) 733 else: 734 raise TypeError("bad boolean value") 735 self._value = 0 736 dispatch["boolean"] = end_boolean 737 738 def end_int(self, data): 739 self.append(int(data)) 740 self._value = 0 741 dispatch["i4"] = end_int 742 dispatch["i8"] = end_int 743 dispatch["int"] = end_int 744 745 def end_double(self, data): 746 self.append(float(data)) 747 self._value = 0 748 dispatch["double"] = end_double 749 750 def end_string(self, data): 751 if self._encoding: 752 data = data.decode(self._encoding) 753 self.append(data) 754 self._value = 0 755 dispatch["string"] = end_string 756 dispatch["name"] = end_string # struct keys are always strings 757 758 def end_array(self, data): 759 mark = self._marks.pop() 760 # map arrays to Python lists 761 self._stack[mark:] = [self._stack[mark:]] 762 self._value = 0 763 dispatch["array"] = end_array 764 765 def end_struct(self, data): 766 mark = self._marks.pop() 767 # map structs to Python dictionaries 768 dict = {} 769 items = self._stack[mark:] 770 for i in range(0, len(items), 2): 771 dict[items[i]] = items[i+1] 772 self._stack[mark:] = [dict] 773 self._value = 0 774 dispatch["struct"] = end_struct 775 776 def end_base64(self, data): 777 value = Binary() 778 value.decode(data.encode("ascii")) 779 if self._use_bytes: 780 value = value.data 781 self.append(value) 782 self._value = 0 783 dispatch["base64"] = end_base64 784 785 def end_dateTime(self, data): 786 value = DateTime() 787 value.decode(data) 788 if self._use_datetime: 789 value = _datetime_type(data) 790 self.append(value) 791 dispatch["dateTime.iso8601"] = end_dateTime 792 793 def end_value(self, data): 794 # if we stumble upon a value element with no internal 795 # elements, treat it as a string element 796 if self._value: 797 self.end_string(data) 798 dispatch["value"] = end_value 799 800 def end_params(self, data): 801 self._type = "params" 802 dispatch["params"] = end_params 803 804 def end_fault(self, data): 805 self._type = "fault" 806 dispatch["fault"] = end_fault 807 808 def end_methodName(self, data): 809 if self._encoding: 810 data = data.decode(self._encoding) 811 self._methodname = data 812 self._type = "methodName" # no params 813 dispatch["methodName"] = end_methodName 814 815## Multicall support 816# 817 818class _MultiCallMethod(object): 819 # some lesser magic to store calls made to a MultiCall object 820 # for batch execution 821 def __init__(self, call_list, name): 822 self.__call_list = call_list 823 self.__name = name 824 def __getattr__(self, name): 825 return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name)) 826 def __call__(self, *args): 827 self.__call_list.append((self.__name, args)) 828 829class MultiCallIterator(object): 830 """Iterates over the results of a multicall. Exceptions are 831 raised in response to xmlrpc faults.""" 832 833 def __init__(self, results): 834 self.results = results 835 836 def __getitem__(self, i): 837 item = self.results[i] 838 if isinstance(type(item), dict): 839 raise Fault(item['faultCode'], item['faultString']) 840 elif type(item) == type([]): 841 return item[0] 842 else: 843 raise ValueError("unexpected type in multicall result") 844 845class MultiCall(object): 846 """server -> a object used to boxcar method calls 847 848 server should be a ServerProxy object. 849 850 Methods can be added to the MultiCall using normal 851 method call syntax e.g.: 852 853 multicall = MultiCall(server_proxy) 854 multicall.add(2,3) 855 multicall.get_address("Guido") 856 857 To execute the multicall, call the MultiCall object e.g.: 858 859 add_result, address = multicall() 860 """ 861 862 def __init__(self, server): 863 self.__server = server 864 self.__call_list = [] 865 866 def __repr__(self): 867 return "<MultiCall at %x>" % id(self) 868 869 __str__ = __repr__ 870 871 def __getattr__(self, name): 872 return _MultiCallMethod(self.__call_list, name) 873 874 def __call__(self): 875 marshalled_list = [] 876 for name, args in self.__call_list: 877 marshalled_list.append({'methodName' : name, 'params' : args}) 878 879 return MultiCallIterator(self.__server.system.multicall(marshalled_list)) 880 881# -------------------------------------------------------------------- 882# convenience functions 883 884FastMarshaller = FastParser = FastUnmarshaller = None 885 886## 887# Create a parser object, and connect it to an unmarshalling instance. 888# This function picks the fastest available XML parser. 889# 890# return A (parser, unmarshaller) tuple. 891 892def getparser(use_datetime=False, use_builtin_types=False): 893 """getparser() -> parser, unmarshaller 894 895 Create an instance of the fastest available parser, and attach it 896 to an unmarshalling object. Return both objects. 897 """ 898 if FastParser and FastUnmarshaller: 899 if use_builtin_types: 900 mkdatetime = _datetime_type 901 mkbytes = base64.decodebytes 902 elif use_datetime: 903 mkdatetime = _datetime_type 904 mkbytes = _binary 905 else: 906 mkdatetime = _datetime 907 mkbytes = _binary 908 target = FastUnmarshaller(True, False, mkbytes, mkdatetime, Fault) 909 parser = FastParser(target) 910 else: 911 target = Unmarshaller(use_datetime=use_datetime, use_builtin_types=use_builtin_types) 912 if FastParser: 913 parser = FastParser(target) 914 else: 915 parser = ExpatParser(target) 916 return parser, target 917 918## 919# Convert a Python tuple or a Fault instance to an XML-RPC packet. 920# 921# @def dumps(params, **options) 922# @param params A tuple or Fault instance. 923# @keyparam methodname If given, create a methodCall request for 924# this method name. 925# @keyparam methodresponse If given, create a methodResponse packet. 926# If used with a tuple, the tuple must be a singleton (that is, 927# it must contain exactly one element). 928# @keyparam encoding The packet encoding. 929# @return A string containing marshalled data. 930 931def dumps(params, methodname=None, methodresponse=None, encoding=None, 932 allow_none=False): 933 """data [,options] -> marshalled data 934 935 Convert an argument tuple or a Fault instance to an XML-RPC 936 request (or response, if the methodresponse option is used). 937 938 In addition to the data object, the following options can be given 939 as keyword arguments: 940 941 methodname: the method name for a methodCall packet 942 943 methodresponse: true to create a methodResponse packet. 944 If this option is used with a tuple, the tuple must be 945 a singleton (i.e. it can contain only one element). 946 947 encoding: the packet encoding (default is UTF-8) 948 949 All byte strings in the data structure are assumed to use the 950 packet encoding. Unicode strings are automatically converted, 951 where necessary. 952 """ 953 954 assert isinstance(params, (tuple, Fault)), "argument must be tuple or Fault instance" 955 if isinstance(params, Fault): 956 methodresponse = 1 957 elif methodresponse and isinstance(params, tuple): 958 assert len(params) == 1, "response tuple must be a singleton" 959 960 if not encoding: 961 encoding = "utf-8" 962 963 if FastMarshaller: 964 m = FastMarshaller(encoding) 965 else: 966 m = Marshaller(encoding, allow_none) 967 968 data = m.dumps(params) 969 970 if encoding != "utf-8": 971 xmlheader = "<?xml version='1.0' encoding='%s'?>\n" % str(encoding) 972 else: 973 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default 974 975 # standard XML-RPC wrappings 976 if methodname: 977 # a method call 978 if not isinstance(methodname, str): 979 methodname = methodname.encode(encoding) 980 data = ( 981 xmlheader, 982 "<methodCall>\n" 983 "<methodName>", methodname, "</methodName>\n", 984 data, 985 "</methodCall>\n" 986 ) 987 elif methodresponse: 988 # a method response, or a fault structure 989 data = ( 990 xmlheader, 991 "<methodResponse>\n", 992 data, 993 "</methodResponse>\n" 994 ) 995 else: 996 return data # return as is 997 return str("").join(data) 998 999## 1000# Convert an XML-RPC packet to a Python object. If the XML-RPC packet 1001# represents a fault condition, this function raises a Fault exception. 1002# 1003# @param data An XML-RPC packet, given as an 8-bit string. 1004# @return A tuple containing the unpacked data, and the method name 1005# (None if not present). 1006# @see Fault 1007 1008def loads(data, use_datetime=False, use_builtin_types=False): 1009 """data -> unmarshalled data, method name 1010 1011 Convert an XML-RPC packet to unmarshalled data plus a method 1012 name (None if not present). 1013 1014 If the XML-RPC packet represents a fault condition, this function 1015 raises a Fault exception. 1016 """ 1017 p, u = getparser(use_datetime=use_datetime, use_builtin_types=use_builtin_types) 1018 p.feed(data) 1019 p.close() 1020 return u.close(), u.getmethodname() 1021 1022## 1023# Encode a string using the gzip content encoding such as specified by the 1024# Content-Encoding: gzip 1025# in the HTTP header, as described in RFC 1952 1026# 1027# @param data the unencoded data 1028# @return the encoded data 1029 1030def gzip_encode(data): 1031 """data -> gzip encoded data 1032 1033 Encode data using the gzip content encoding as described in RFC 1952 1034 """ 1035 if not gzip: 1036 raise NotImplementedError 1037 f = BytesIO() 1038 gzf = gzip.GzipFile(mode="wb", fileobj=f, compresslevel=1) 1039 gzf.write(data) 1040 gzf.close() 1041 encoded = f.getvalue() 1042 f.close() 1043 return encoded 1044 1045## 1046# Decode a string using the gzip content encoding such as specified by the 1047# Content-Encoding: gzip 1048# in the HTTP header, as described in RFC 1952 1049# 1050# @param data The encoded data 1051# @return the unencoded data 1052# @raises ValueError if data is not correctly coded. 1053 1054def gzip_decode(data): 1055 """gzip encoded data -> unencoded data 1056 1057 Decode data using the gzip content encoding as described in RFC 1952 1058 """ 1059 if not gzip: 1060 raise NotImplementedError 1061 f = BytesIO(data) 1062 gzf = gzip.GzipFile(mode="rb", fileobj=f) 1063 try: 1064 decoded = gzf.read() 1065 except IOError: 1066 raise ValueError("invalid data") 1067 f.close() 1068 gzf.close() 1069 return decoded 1070 1071## 1072# Return a decoded file-like object for the gzip encoding 1073# as described in RFC 1952. 1074# 1075# @param response A stream supporting a read() method 1076# @return a file-like object that the decoded data can be read() from 1077 1078class GzipDecodedResponse(gzip.GzipFile if gzip else object): 1079 """a file-like object to decode a response encoded with the gzip 1080 method, as described in RFC 1952. 1081 """ 1082 def __init__(self, response): 1083 #response doesn't support tell() and read(), required by 1084 #GzipFile 1085 if not gzip: 1086 raise NotImplementedError 1087 self.io = BytesIO(response.read()) 1088 gzip.GzipFile.__init__(self, mode="rb", fileobj=self.io) 1089 1090 def close(self): 1091 gzip.GzipFile.close(self) 1092 self.io.close() 1093 1094 1095# -------------------------------------------------------------------- 1096# request dispatcher 1097 1098class _Method(object): 1099 # some magic to bind an XML-RPC method to an RPC server. 1100 # supports "nested" methods (e.g. examples.getStateName) 1101 def __init__(self, send, name): 1102 self.__send = send 1103 self.__name = name 1104 def __getattr__(self, name): 1105 return _Method(self.__send, "%s.%s" % (self.__name, name)) 1106 def __call__(self, *args): 1107 return self.__send(self.__name, args) 1108 1109## 1110# Standard transport class for XML-RPC over HTTP. 1111# <p> 1112# You can create custom transports by subclassing this method, and 1113# overriding selected methods. 1114 1115class Transport(object): 1116 """Handles an HTTP transaction to an XML-RPC server.""" 1117 1118 # client identifier (may be overridden) 1119 user_agent = "Python-xmlrpc/%s" % __version__ 1120 1121 #if true, we'll request gzip encoding 1122 accept_gzip_encoding = True 1123 1124 # if positive, encode request using gzip if it exceeds this threshold 1125 # note that many server will get confused, so only use it if you know 1126 # that they can decode such a request 1127 encode_threshold = None #None = don't encode 1128 1129 def __init__(self, use_datetime=False, use_builtin_types=False): 1130 self._use_datetime = use_datetime 1131 self._use_builtin_types = use_builtin_types 1132 self._connection = (None, None) 1133 self._extra_headers = [] 1134 1135 ## 1136 # Send a complete request, and parse the response. 1137 # Retry request if a cached connection has disconnected. 1138 # 1139 # @param host Target host. 1140 # @param handler Target PRC handler. 1141 # @param request_body XML-RPC request body. 1142 # @param verbose Debugging flag. 1143 # @return Parsed response. 1144 1145 def request(self, host, handler, request_body, verbose=False): 1146 #retry request once if cached connection has gone cold 1147 for i in (0, 1): 1148 try: 1149 return self.single_request(host, handler, request_body, verbose) 1150 except socket.error as e: 1151 if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE): 1152 raise 1153 except http_client.BadStatusLine: #close after we sent request 1154 if i: 1155 raise 1156 1157 def single_request(self, host, handler, request_body, verbose=False): 1158 # issue XML-RPC request 1159 try: 1160 http_conn = self.send_request(host, handler, request_body, verbose) 1161 resp = http_conn.getresponse() 1162 if resp.status == 200: 1163 self.verbose = verbose 1164 return self.parse_response(resp) 1165 1166 except Fault: 1167 raise 1168 except Exception: 1169 #All unexpected errors leave connection in 1170 # a strange state, so we clear it. 1171 self.close() 1172 raise 1173 1174 #We got an error response. 1175 #Discard any response data and raise exception 1176 if resp.getheader("content-length", ""): 1177 resp.read() 1178 raise ProtocolError( 1179 host + handler, 1180 resp.status, resp.reason, 1181 dict(resp.getheaders()) 1182 ) 1183 1184 1185 ## 1186 # Create parser. 1187 # 1188 # @return A 2-tuple containing a parser and a unmarshaller. 1189 1190 def getparser(self): 1191 # get parser and unmarshaller 1192 return getparser(use_datetime=self._use_datetime, 1193 use_builtin_types=self._use_builtin_types) 1194 1195 ## 1196 # Get authorization info from host parameter 1197 # Host may be a string, or a (host, x509-dict) tuple; if a string, 1198 # it is checked for a "user:pw@host" format, and a "Basic 1199 # Authentication" header is added if appropriate. 1200 # 1201 # @param host Host descriptor (URL or (URL, x509 info) tuple). 1202 # @return A 3-tuple containing (actual host, extra headers, 1203 # x509 info). The header and x509 fields may be None. 1204 1205 def get_host_info(self, host): 1206 1207 x509 = {} 1208 if isinstance(host, tuple): 1209 host, x509 = host 1210 1211 auth, host = urllib_parse.splituser(host) 1212 1213 if auth: 1214 auth = urllib_parse.unquote_to_bytes(auth) 1215 auth = base64.encodebytes(auth).decode("utf-8") 1216 auth = "".join(auth.split()) # get rid of whitespace 1217 extra_headers = [ 1218 ("Authorization", "Basic " + auth) 1219 ] 1220 else: 1221 extra_headers = [] 1222 1223 return host, extra_headers, x509 1224 1225 ## 1226 # Connect to server. 1227 # 1228 # @param host Target host. 1229 # @return An HTTPConnection object 1230 1231 def make_connection(self, host): 1232 #return an existing connection if possible. This allows 1233 #HTTP/1.1 keep-alive. 1234 if self._connection and host == self._connection[0]: 1235 return self._connection[1] 1236 # create a HTTP connection object from a host descriptor 1237 chost, self._extra_headers, x509 = self.get_host_info(host) 1238 self._connection = host, http_client.HTTPConnection(chost) 1239 return self._connection[1] 1240 1241 ## 1242 # Clear any cached connection object. 1243 # Used in the event of socket errors. 1244 # 1245 def close(self): 1246 if self._connection[1]: 1247 self._connection[1].close() 1248 self._connection = (None, None) 1249 1250 ## 1251 # Send HTTP request. 1252 # 1253 # @param host Host descriptor (URL or (URL, x509 info) tuple). 1254 # @param handler Targer RPC handler (a path relative to host) 1255 # @param request_body The XML-RPC request body 1256 # @param debug Enable debugging if debug is true. 1257 # @return An HTTPConnection. 1258 1259 def send_request(self, host, handler, request_body, debug): 1260 connection = self.make_connection(host) 1261 headers = self._extra_headers[:] 1262 if debug: 1263 connection.set_debuglevel(1) 1264 if self.accept_gzip_encoding and gzip: 1265 connection.putrequest("POST", handler, skip_accept_encoding=True) 1266 headers.append(("Accept-Encoding", "gzip")) 1267 else: 1268 connection.putrequest("POST", handler) 1269 headers.append(("Content-Type", "text/xml")) 1270 headers.append(("User-Agent", self.user_agent)) 1271 self.send_headers(connection, headers) 1272 self.send_content(connection, request_body) 1273 return connection 1274 1275 ## 1276 # Send request headers. 1277 # This function provides a useful hook for subclassing 1278 # 1279 # @param connection httpConnection. 1280 # @param headers list of key,value pairs for HTTP headers 1281 1282 def send_headers(self, connection, headers): 1283 for key, val in headers: 1284 connection.putheader(key, val) 1285 1286 ## 1287 # Send request body. 1288 # This function provides a useful hook for subclassing 1289 # 1290 # @param connection httpConnection. 1291 # @param request_body XML-RPC request body. 1292 1293 def send_content(self, connection, request_body): 1294 #optionally encode the request 1295 if (self.encode_threshold is not None and 1296 self.encode_threshold < len(request_body) and 1297 gzip): 1298 connection.putheader("Content-Encoding", "gzip") 1299 request_body = gzip_encode(request_body) 1300 1301 connection.putheader("Content-Length", str(len(request_body))) 1302 connection.endheaders(request_body) 1303 1304 ## 1305 # Parse response. 1306 # 1307 # @param file Stream. 1308 # @return Response tuple and target method. 1309 1310 def parse_response(self, response): 1311 # read response data from httpresponse, and parse it 1312 # Check for new http response object, otherwise it is a file object. 1313 if hasattr(response, 'getheader'): 1314 if response.getheader("Content-Encoding", "") == "gzip": 1315 stream = GzipDecodedResponse(response) 1316 else: 1317 stream = response 1318 else: 1319 stream = response 1320 1321 p, u = self.getparser() 1322 1323 while 1: 1324 data = stream.read(1024) 1325 if not data: 1326 break 1327 if self.verbose: 1328 print("body:", repr(data)) 1329 p.feed(data) 1330 1331 if stream is not response: 1332 stream.close() 1333 p.close() 1334 1335 return u.close() 1336 1337## 1338# Standard transport class for XML-RPC over HTTPS. 1339 1340class SafeTransport(Transport): 1341 """Handles an HTTPS transaction to an XML-RPC server.""" 1342 1343 # FIXME: mostly untested 1344 1345 def make_connection(self, host): 1346 if self._connection and host == self._connection[0]: 1347 return self._connection[1] 1348 1349 if not hasattr(http_client, "HTTPSConnection"): 1350 raise NotImplementedError( 1351 "your version of http.client doesn't support HTTPS") 1352 # create a HTTPS connection object from a host descriptor 1353 # host may be a string, or a (host, x509-dict) tuple 1354 chost, self._extra_headers, x509 = self.get_host_info(host) 1355 self._connection = host, http_client.HTTPSConnection(chost, 1356 None, **(x509 or {})) 1357 return self._connection[1] 1358 1359## 1360# Standard server proxy. This class establishes a virtual connection 1361# to an XML-RPC server. 1362# <p> 1363# This class is available as ServerProxy and Server. New code should 1364# use ServerProxy, to avoid confusion. 1365# 1366# @def ServerProxy(uri, **options) 1367# @param uri The connection point on the server. 1368# @keyparam transport A transport factory, compatible with the 1369# standard transport class. 1370# @keyparam encoding The default encoding used for 8-bit strings 1371# (default is UTF-8). 1372# @keyparam verbose Use a true value to enable debugging output. 1373# (printed to standard output). 1374# @see Transport 1375 1376class ServerProxy(object): 1377 """uri [,options] -> a logical connection to an XML-RPC server 1378 1379 uri is the connection point on the server, given as 1380 scheme://host/target. 1381 1382 The standard implementation always supports the "http" scheme. If 1383 SSL socket support is available (Python 2.0), it also supports 1384 "https". 1385 1386 If the target part and the slash preceding it are both omitted, 1387 "/RPC2" is assumed. 1388 1389 The following options can be given as keyword arguments: 1390 1391 transport: a transport factory 1392 encoding: the request encoding (default is UTF-8) 1393 1394 All 8-bit strings passed to the server proxy are assumed to use 1395 the given encoding. 1396 """ 1397 1398 def __init__(self, uri, transport=None, encoding=None, verbose=False, 1399 allow_none=False, use_datetime=False, use_builtin_types=False): 1400 # establish a "logical" server connection 1401 1402 # get the url 1403 type, uri = urllib_parse.splittype(uri) 1404 if type not in ("http", "https"): 1405 raise IOError("unsupported XML-RPC protocol") 1406 self.__host, self.__handler = urllib_parse.splithost(uri) 1407 if not self.__handler: 1408 self.__handler = "/RPC2" 1409 1410 if transport is None: 1411 if type == "https": 1412 handler = SafeTransport 1413 else: 1414 handler = Transport 1415 transport = handler(use_datetime=use_datetime, 1416 use_builtin_types=use_builtin_types) 1417 self.__transport = transport 1418 1419 self.__encoding = encoding or 'utf-8' 1420 self.__verbose = verbose 1421 self.__allow_none = allow_none 1422 1423 def __close(self): 1424 self.__transport.close() 1425 1426 def __request(self, methodname, params): 1427 # call a method on the remote server 1428 1429 request = dumps(params, methodname, encoding=self.__encoding, 1430 allow_none=self.__allow_none).encode(self.__encoding) 1431 1432 response = self.__transport.request( 1433 self.__host, 1434 self.__handler, 1435 request, 1436 verbose=self.__verbose 1437 ) 1438 1439 if len(response) == 1: 1440 response = response[0] 1441 1442 return response 1443 1444 def __repr__(self): 1445 return ( 1446 "<ServerProxy for %s%s>" % 1447 (self.__host, self.__handler) 1448 ) 1449 1450 __str__ = __repr__ 1451 1452 def __getattr__(self, name): 1453 # magic method dispatcher 1454 return _Method(self.__request, name) 1455 1456 # note: to call a remote object with an non-standard name, use 1457 # result getattr(server, "strange-python-name")(args) 1458 1459 def __call__(self, attr): 1460 """A workaround to get special attributes on the ServerProxy 1461 without interfering with the magic __getattr__ 1462 """ 1463 if attr == "close": 1464 return self.__close 1465 elif attr == "transport": 1466 return self.__transport 1467 raise AttributeError("Attribute %r not found" % (attr,)) 1468 1469# compatibility 1470 1471Server = ServerProxy 1472 1473# -------------------------------------------------------------------- 1474# test code 1475 1476if __name__ == "__main__": 1477 1478 # simple test program (from the XML-RPC specification) 1479 1480 # local server, available from Lib/xmlrpc/server.py 1481 server = ServerProxy("http://localhost:8000") 1482 1483 try: 1484 print(server.currentTime.getCurrentTime()) 1485 except Error as v: 1486 print("ERROR", v) 1487 1488 multi = MultiCall(server) 1489 multi.getData() 1490 multi.pow(2,9) 1491 multi.add(1,2) 1492 try: 1493 for response in multi(): 1494 print(response) 1495 except Error as v: 1496 print("ERROR", v) 1497