codekingpro/portable-devtools
114k
1#2# XML-RPC CLIENT LIBRARY3# $Id$4#5# an XML-RPC client interface for Python.6#7# the marshalling and response parser code can also be used to8# 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 Created15# 1999-01-15 fl Changed dateTime to use localtime16# 1999-01-16 fl Added Binary/base64 element, default to RPC2 service17# 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 argument23# 2001-02-24 fl Added encoding/Unicode/SafeTransport patches24# 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 singleton26# 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 getparser30# 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 them32# 2001-10-01 fl Use faster escape method (80% dumps speedup)33# 2001-10-02 fl More dumps microtuning34# 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 overflow36# 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 comments40# 2002-04-16 fl Added __str__ methods to datetime/binary wrappers41# 2002-05-15 fl Added error constants (from Andrew Kuchling)42# 2002-06-27 fl Merged with Python CVS version43# 2002-10-22 fl Added basic authentication (based on code from Phillip Eby)44# 2003-01-22 sm Add support for the bool type45# 2003-02-27 gvr Remove apply calls46# 2003-04-24 sm Use cStringIO if available47# 2003-04-25 ak Add support for nil48# 2003-06-15 gn Add support for time.struct_time49# 2003-07-12 gp Correct marshalling of Faults50# 2003-10-31 mvl Add multicall support51# 2004-08-20 mvl Bump minimum supported Python version to 2.152# 2014-12-02 ch/doko Add workaround for gzip bomb vulnerability53#54# Copyright (c) 1999-2002 by Secret Labs AB.55# Copyright (c) 1999-2002 by Fredrik Lundh.56#57# info@pythonware.com58# http://www.pythonware.com59#60# --------------------------------------------------------------------61# The XML-RPC client interface is62#63# Copyright (c) 1999-2002 by Secret Labs AB64# Copyright (c) 1999-2002 by Fredrik Lundh65#66# By obtaining, using, and/or copying this software and/or its67# associated documentation, you agree that you have read, understood,68# and will comply with the following terms and conditions:69#70# Permission to use, copy, modify, and distribute this software and71# its associated documentation for any purpose and without fee is72# hereby granted, provided that the above copyright notice appears in73# all copies, and that both that copyright notice and this permission74# notice appear in supporting documentation, and that the name of75# Secret Labs AB or the author not be used in advertising or publicity76# pertaining to distribution of the software without specific, written77# prior permission.78#79# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD80# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-81# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR82# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY83# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,84# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS85# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE86# OF THIS SOFTWARE.87# --------------------------------------------------------------------88 89"""90An XML-RPC client interface for Python.91 92The marshalling and response parser code can also be used to93implement XML-RPC servers.94 95Exported exceptions:96 97 Error Base class for client errors98 ProtocolError Indicates an HTTP protocol error99 ResponseError Indicates a broken response package100 Fault Indicates an XML-RPC fault package101 102Exported classes:103 104 ServerProxy Represents a logical connection to an XML-RPC server105 106 MultiCall Executor of boxcared xmlrpc requests107 DateTime dateTime wrapper for an ISO 8601 string or time tuple or108 localtime integer value to generate a "dateTime.iso8601"109 XML-RPC value110 Binary binary data wrapper111 112 Marshaller Generate an XML-RPC params chunk from a Python data structure113 Unmarshaller Unmarshal an XML-RPC response from incoming XML event message114 Transport Handles an HTTP transaction to an XML-RPC server115 SafeTransport Handles an HTTPS transaction to an XML-RPC server116 117Exported constants:118 119 (none)120 121Exported functions:122 123 getparser Create instance of the fastest available parser & attach124 to an unmarshalling object125 dumps Convert an argument tuple or a Fault instance to an XML-RPC126 request (or response, if the methodresponse option is used).127 loads Convert an XML-RPC packet to unmarshalled data plus a method128 name (None if not present).129"""130 131import base64132import sys133import time134from datetime import datetime135from decimal import Decimal136import http.client137import urllib.parse138from xml.parsers import expat139import errno140from io import BytesIO141try:142 import gzip143except ImportError:144 gzip = None #python can be built without zlib/gzip support145 146# --------------------------------------------------------------------147# Internal stuff148 149def escape(s):150 s = s.replace("&", "&")151 s = s.replace("<", "<")152 return s.replace(">", ">",)153 154# used in User-Agent header sent155__version__ = '%d.%d' % sys.version_info[:2]156 157# xmlrpc integer limits158MAXINT = 2**31-1159MININT = -2**31160 161# --------------------------------------------------------------------162# Error constants (from Dan Libby's specification at163# http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php)164 165# Ranges of errors166PARSE_ERROR = -32700167SERVER_ERROR = -32600168APPLICATION_ERROR = -32500169SYSTEM_ERROR = -32400170TRANSPORT_ERROR = -32300171 172# Specific errors173NOT_WELLFORMED_ERROR = -32700174UNSUPPORTED_ENCODING = -32701175INVALID_ENCODING_CHAR = -32702176INVALID_XMLRPC = -32600177METHOD_NOT_FOUND = -32601178INVALID_METHOD_PARAMS = -32602179INTERNAL_ERROR = -32603180 181# --------------------------------------------------------------------182# Exceptions183 184##185# Base class for all kinds of client-side errors.186 187class Error(Exception):188 """Base class for client errors."""189 __str__ = object.__str__190 191##192# Indicates an HTTP-level protocol error. This is raised by the HTTP193# transport layer, if the server returns an error code other than 200194# (OK).195#196# @param url The target URL.197# @param errcode The HTTP error code.198# @param errmsg The HTTP error message.199# @param headers The HTTP header dictionary.200 201class ProtocolError(Error):202 """Indicates an HTTP protocol error."""203 def __init__(self, url, errcode, errmsg, headers):204 Error.__init__(self)205 self.url = url206 self.errcode = errcode207 self.errmsg = errmsg208 self.headers = headers209 def __repr__(self):210 return (211 "<%s for %s: %s %s>" %212 (self.__class__.__name__, self.url, self.errcode, self.errmsg)213 )214 215##216# Indicates a broken XML-RPC response package. This exception is217# raised by the unmarshalling layer, if the XML-RPC response is218# malformed.219 220class ResponseError(Error):221 """Indicates a broken response package."""222 pass223 224##225# Indicates an XML-RPC fault response package. This exception is226# raised by the unmarshalling layer, if the XML-RPC response contains227# a fault string. This exception can also be used as a class, to228# generate a fault XML-RPC message.229#230# @param faultCode The XML-RPC fault code.231# @param faultString The XML-RPC fault string.232 233class Fault(Error):234 """Indicates an XML-RPC fault package."""235 def __init__(self, faultCode, faultString, **extra):236 Error.__init__(self)237 self.faultCode = faultCode238 self.faultString = faultString239 def __repr__(self):240 return "<%s %s: %r>" % (self.__class__.__name__,241 self.faultCode, self.faultString)242 243# --------------------------------------------------------------------244# Special values245 246##247# Backwards compatibility248boolean = Boolean = bool249 250 251def _iso8601_format(value):252 if value.tzinfo is not None:253 # XML-RPC only uses the naive portion of the datetime254 value = value.replace(tzinfo=None)255 # XML-RPC doesn't use '-' separator in the date part256 return value.isoformat(timespec='seconds').replace('-', '')257 258 259def _strftime(value):260 if isinstance(value, datetime):261 return _iso8601_format(value)262 263 if not isinstance(value, (tuple, time.struct_time)):264 if value == 0:265 value = time.time()266 value = time.localtime(value)267 268 return "%04d%02d%02dT%02d:%02d:%02d" % value[:6]269 270class DateTime:271 """DateTime wrapper for an ISO 8601 string or time tuple or272 localtime integer value to generate 'dateTime.iso8601' XML-RPC273 value.274 """275 276 def __init__(self, value=0):277 if isinstance(value, str):278 self.value = value279 else:280 self.value = _strftime(value)281 282 def make_comparable(self, other):283 if isinstance(other, DateTime):284 s = self.value285 o = other.value286 elif isinstance(other, datetime):287 s = self.value288 o = _iso8601_format(other)289 elif isinstance(other, str):290 s = self.value291 o = other292 elif hasattr(other, "timetuple"):293 s = self.timetuple()294 o = other.timetuple()295 else:296 s = self297 o = NotImplemented298 return s, o299 300 def __lt__(self, other):301 s, o = self.make_comparable(other)302 if o is NotImplemented:303 return NotImplemented304 return s < o305 306 def __le__(self, other):307 s, o = self.make_comparable(other)308 if o is NotImplemented:309 return NotImplemented310 return s <= o311 312 def __gt__(self, other):313 s, o = self.make_comparable(other)314 if o is NotImplemented:315 return NotImplemented316 return s > o317 318 def __ge__(self, other):319 s, o = self.make_comparable(other)320 if o is NotImplemented:321 return NotImplemented322 return s >= o323 324 def __eq__(self, other):325 s, o = self.make_comparable(other)326 if o is NotImplemented:327 return NotImplemented328 return s == o329 330 def timetuple(self):331 return time.strptime(self.value, "%Y%m%dT%H:%M:%S")332 333 ##334 # Get date/time value.335 #336 # @return Date/time value, as an ISO 8601 string.337 338 def __str__(self):339 return self.value340 341 def __repr__(self):342 return "<%s %r at %#x>" % (self.__class__.__name__, self.value, id(self))343 344 def decode(self, data):345 self.value = str(data).strip()346 347 def encode(self, out):348 out.write("<value><dateTime.iso8601>")349 out.write(self.value)350 out.write("</dateTime.iso8601></value>\n")351 352def _datetime(data):353 # decode xml element contents into a DateTime structure.354 value = DateTime()355 value.decode(data)356 return value357 358def _datetime_type(data):359 return datetime.strptime(data, "%Y%m%dT%H:%M:%S")360 361##362# Wrapper for binary data. This can be used to transport any kind363# of binary data over XML-RPC, using BASE64 encoding.364#365# @param data An 8-bit string containing arbitrary data.366 367class Binary:368 """Wrapper for binary data."""369 370 def __init__(self, data=None):371 if data is None:372 data = b""373 else:374 if not isinstance(data, (bytes, bytearray)):375 raise TypeError("expected bytes or bytearray, not %s" %376 data.__class__.__name__)377 data = bytes(data) # Make a copy of the bytes!378 self.data = data379 380 ##381 # Get buffer contents.382 #383 # @return Buffer contents, as an 8-bit string.384 385 def __str__(self):386 return str(self.data, "latin-1") # XXX encoding?!387 388 def __eq__(self, other):389 if isinstance(other, Binary):390 other = other.data391 return self.data == other392 393 def decode(self, data):394 self.data = base64.decodebytes(data)395 396 def encode(self, out):397 out.write("<value><base64>\n")398 encoded = base64.encodebytes(self.data)399 out.write(encoded.decode('ascii'))400 out.write("</base64></value>\n")401 402def _binary(data):403 # decode xml element contents into a Binary structure404 value = Binary()405 value.decode(data)406 return value407 408WRAPPERS = (DateTime, Binary)409 410# --------------------------------------------------------------------411# XML parsers412 413class ExpatParser:414 # fast expat parser for Python 2.0 and later.415 def __init__(self, target):416 self._parser = parser = expat.ParserCreate(None, None)417 self._target = target418 parser.StartElementHandler = target.start419 parser.EndElementHandler = target.end420 parser.CharacterDataHandler = target.data421 encoding = None422 target.xml(encoding, None)423 424 def feed(self, data):425 self._parser.Parse(data, False)426 427 def close(self):428 try:429 parser = self._parser430 except AttributeError:431 pass432 else:433 del self._target, self._parser # get rid of circular references434 parser.Parse(b"", True) # end of data435 436# --------------------------------------------------------------------437# XML-RPC marshalling and unmarshalling code438 439##440# XML-RPC marshaller.441#442# @param encoding Default encoding for 8-bit strings. The default443# value is None (interpreted as UTF-8).444# @see dumps445 446class Marshaller:447 """Generate an XML-RPC params chunk from a Python data structure.448 449 Create a Marshaller instance for each set of parameters, and use450 the "dumps" method to convert your data (represented as a tuple)451 to an XML-RPC params chunk. To write a fault response, pass a452 Fault instance instead. You may prefer to use the "dumps" module453 function for this purpose.454 """455 456 # by the way, if you don't understand what's going on in here,457 # that's perfectly ok.458 459 def __init__(self, encoding=None, allow_none=False):460 self.memo = {}461 self.data = None462 self.encoding = encoding463 self.allow_none = allow_none464 465 dispatch = {}466 467 def dumps(self, values):468 out = []469 write = out.append470 dump = self.__dump471 if isinstance(values, Fault):472 # fault instance473 write("<fault>\n")474 dump({'faultCode': values.faultCode,475 'faultString': values.faultString},476 write)477 write("</fault>\n")478 else:479 # parameter block480 # FIXME: the xml-rpc specification allows us to leave out481 # the entire <params> block if there are no parameters.482 # however, changing this may break older code (including483 # old versions of xmlrpclib.py), so this is better left as484 # is for now. See @XMLRPC3 for more information. /F485 write("<params>\n")486 for v in values:487 write("<param>\n")488 dump(v, write)489 write("</param>\n")490 write("</params>\n")491 result = "".join(out)492 return result493 494 def __dump(self, value, write):495 try:496 f = self.dispatch[type(value)]497 except KeyError:498 # check if this object can be marshalled as a structure499 if not hasattr(value, '__dict__'):500 raise TypeError("cannot marshal %s objects" % type(value))501 # check if this class is a sub-class of a basic type,502 # because we don't know how to marshal these types503 # (e.g. a string sub-class)504 for type_ in type(value).__mro__:505 if type_ in self.dispatch.keys():506 raise TypeError("cannot marshal %s objects" % type(value))507 # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix508 # for the p3yk merge, this should probably be fixed more neatly.509 f = self.dispatch["_arbitrary_instance"]510 f(self, value, write)511 512 def dump_nil (self, value, write):513 if not self.allow_none:514 raise TypeError("cannot marshal None unless allow_none is enabled")515 write("<value><nil/></value>")516 dispatch[type(None)] = dump_nil517 518 def dump_bool(self, value, write):519 write("<value><boolean>")520 write(value and "1" or "0")521 write("</boolean></value>\n")522 dispatch[bool] = dump_bool523 524 def dump_long(self, value, write):525 if value > MAXINT or value < MININT:526 raise OverflowError("int exceeds XML-RPC limits")527 write("<value><int>")528 write(str(int(value)))529 write("</int></value>\n")530 dispatch[int] = dump_long531 532 # backward compatible533 dump_int = dump_long534 535 def dump_double(self, value, write):536 write("<value><double>")537 write(repr(value))538 write("</double></value>\n")539 dispatch[float] = dump_double540 541 def dump_unicode(self, value, write, escape=escape):542 write("<value><string>")543 write(escape(value))544 write("</string></value>\n")545 dispatch[str] = dump_unicode546 547 def dump_bytes(self, value, write):548 write("<value><base64>\n")549 encoded = base64.encodebytes(value)550 write(encoded.decode('ascii'))551 write("</base64></value>\n")552 dispatch[bytes] = dump_bytes553 dispatch[bytearray] = dump_bytes554 555 def dump_array(self, value, write):556 i = id(value)557 if i in self.memo:558 raise TypeError("cannot marshal recursive sequences")559 self.memo[i] = None560 dump = self.__dump561 write("<value><array><data>\n")562 for v in value:563 dump(v, write)564 write("</data></array></value>\n")565 del self.memo[i]566 dispatch[tuple] = dump_array567 dispatch[list] = dump_array568 569 def dump_struct(self, value, write, escape=escape):570 i = id(value)571 if i in self.memo:572 raise TypeError("cannot marshal recursive dictionaries")573 self.memo[i] = None574 dump = self.__dump575 write("<value><struct>\n")576 for k, v in value.items():577 write("<member>\n")578 if not isinstance(k, str):579 raise TypeError("dictionary key must be string")580 write("<name>%s</name>\n" % escape(k))581 dump(v, write)582 write("</member>\n")583 write("</struct></value>\n")584 del self.memo[i]585 dispatch[dict] = dump_struct586 587 def dump_datetime(self, value, write):588 write("<value><dateTime.iso8601>")589 write(_strftime(value))590 write("</dateTime.iso8601></value>\n")591 dispatch[datetime] = dump_datetime592 593 def dump_instance(self, value, write):594 # check for special wrappers595 if value.__class__ in WRAPPERS:596 self.write = write597 value.encode(self)598 del self.write599 else:600 # store instance attributes as a struct (really?)601 self.dump_struct(value.__dict__, write)602 dispatch[DateTime] = dump_instance603 dispatch[Binary] = dump_instance604 # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix605 # for the p3yk merge, this should probably be fixed more neatly.606 dispatch["_arbitrary_instance"] = dump_instance607 608##609# XML-RPC unmarshaller.610#611# @see loads612 613class Unmarshaller:614 """Unmarshal an XML-RPC response, based on incoming XML event615 messages (start, data, end). Call close() to get the resulting616 data structure.617 618 Note that this reader is fairly tolerant, and gladly accepts bogus619 XML-RPC data without complaining (but not bogus XML).620 """621 622 # and again, if you don't understand what's going on in here,623 # that's perfectly ok.624 625 def __init__(self, use_datetime=False, use_builtin_types=False):626 self._type = None627 self._stack = []628 self._marks = []629 self._data = []630 self._value = False631 self._methodname = None632 self._encoding = "utf-8"633 self.append = self._stack.append634 self._use_datetime = use_builtin_types or use_datetime635 self._use_bytes = use_builtin_types636 637 def close(self):638 # return response tuple and target method639 if self._type is None or self._marks:640 raise ResponseError()641 if self._type == "fault":642 raise Fault(**self._stack[0])643 return tuple(self._stack)644 645 def getmethodname(self):646 return self._methodname647 648 #649 # event handlers650 651 def xml(self, encoding, standalone):652 self._encoding = encoding653 # FIXME: assert standalone == 1 ???654 655 def start(self, tag, attrs):656 # prepare to handle this element657 if ':' in tag:658 tag = tag.split(':')[-1]659 if tag == "array" or tag == "struct":660 self._marks.append(len(self._stack))661 self._data = []662 if self._value and tag not in self.dispatch:663 raise ResponseError("unknown tag %r" % tag)664 self._value = (tag == "value")665 666 def data(self, text):667 self._data.append(text)668 669 def end(self, tag):670 # call the appropriate end tag handler671 try:672 f = self.dispatch[tag]673 except KeyError:674 if ':' not in tag:675 return # unknown tag ?676 try:677 f = self.dispatch[tag.split(':')[-1]]678 except KeyError:679 return # unknown tag ?680 return f(self, "".join(self._data))681 682 #683 # accelerator support684 685 def end_dispatch(self, tag, data):686 # dispatch data687 try:688 f = self.dispatch[tag]689 except KeyError:690 if ':' not in tag:691 return # unknown tag ?692 try:693 f = self.dispatch[tag.split(':')[-1]]694 except KeyError:695 return # unknown tag ?696 return f(self, data)697 698 #699 # element decoders700 701 dispatch = {}702 703 def end_nil (self, data):704 self.append(None)705 self._value = 0706 dispatch["nil"] = end_nil707 708 def end_boolean(self, data):709 if data == "0":710 self.append(False)711 elif data == "1":712 self.append(True)713 else:714 raise TypeError("bad boolean value")715 self._value = 0716 dispatch["boolean"] = end_boolean717 718 def end_int(self, data):719 self.append(int(data))720 self._value = 0721 dispatch["i1"] = end_int722 dispatch["i2"] = end_int723 dispatch["i4"] = end_int724 dispatch["i8"] = end_int725 dispatch["int"] = end_int726 dispatch["biginteger"] = end_int727 728 def end_double(self, data):729 self.append(float(data))730 self._value = 0731 dispatch["double"] = end_double732 dispatch["float"] = end_double733 734 def end_bigdecimal(self, data):735 self.append(Decimal(data))736 self._value = 0737 dispatch["bigdecimal"] = end_bigdecimal738 739 def end_string(self, data):740 if self._encoding:741 data = data.decode(self._encoding)742 self.append(data)743 self._value = 0744 dispatch["string"] = end_string745 dispatch["name"] = end_string # struct keys are always strings746 747 def end_array(self, data):748 mark = self._marks.pop()749 # map arrays to Python lists750 self._stack[mark:] = [self._stack[mark:]]751 self._value = 0752 dispatch["array"] = end_array753 754 def end_struct(self, data):755 mark = self._marks.pop()756 # map structs to Python dictionaries757 dict = {}758 items = self._stack[mark:]759 for i in range(0, len(items), 2):760 dict[items[i]] = items[i+1]761 self._stack[mark:] = [dict]762 self._value = 0763 dispatch["struct"] = end_struct764 765 def end_base64(self, data):766 value = Binary()767 value.decode(data.encode("ascii"))768 if self._use_bytes:769 value = value.data770 self.append(value)771 self._value = 0772 dispatch["base64"] = end_base64773 774 def end_dateTime(self, data):775 value = DateTime()776 value.decode(data)777 if self._use_datetime:778 value = _datetime_type(data)779 self.append(value)780 dispatch["dateTime.iso8601"] = end_dateTime781 782 def end_value(self, data):783 # if we stumble upon a value element with no internal784 # elements, treat it as a string element785 if self._value:786 self.end_string(data)787 dispatch["value"] = end_value788 789 def end_params(self, data):790 self._type = "params"791 dispatch["params"] = end_params792 793 def end_fault(self, data):794 self._type = "fault"795 dispatch["fault"] = end_fault796 797 def end_methodName(self, data):798 if self._encoding:799 data = data.decode(self._encoding)800 self._methodname = data801 self._type = "methodName" # no params802 dispatch["methodName"] = end_methodName803 804## Multicall support805#806 807class _MultiCallMethod:808 # some lesser magic to store calls made to a MultiCall object809 # for batch execution810 def __init__(self, call_list, name):811 self.__call_list = call_list812 self.__name = name813 def __getattr__(self, name):814 return _MultiCallMethod(self.__call_list, "%s.%s" % (self.__name, name))815 def __call__(self, *args):816 self.__call_list.append((self.__name, args))817 818class MultiCallIterator:819 """Iterates over the results of a multicall. Exceptions are820 raised in response to xmlrpc faults."""821 822 def __init__(self, results):823 self.results = results824 825 def __getitem__(self, i):826 item = self.results[i]827 if isinstance(item, dict):828 raise Fault(item['faultCode'], item['faultString'])829 elif isinstance(item, list):830 return item[0]831 else:832 raise ValueError("unexpected type in multicall result")833 834class MultiCall:835 """server -> an object used to boxcar method calls836 837 server should be a ServerProxy object.838 839 Methods can be added to the MultiCall using normal840 method call syntax e.g.:841 842 multicall = MultiCall(server_proxy)843 multicall.add(2,3)844 multicall.get_address("Guido")845 846 To execute the multicall, call the MultiCall object e.g.:847 848 add_result, address = multicall()849 """850 851 def __init__(self, server):852 self.__server = server853 self.__call_list = []854 855 def __repr__(self):856 return "<%s at %#x>" % (self.__class__.__name__, id(self))857 858 def __getattr__(self, name):859 return _MultiCallMethod(self.__call_list, name)860 861 def __call__(self):862 marshalled_list = []863 for name, args in self.__call_list:864 marshalled_list.append({'methodName' : name, 'params' : args})865 866 return MultiCallIterator(self.__server.system.multicall(marshalled_list))867 868# --------------------------------------------------------------------869# convenience functions870 871FastMarshaller = FastParser = FastUnmarshaller = None872 873##874# Create a parser object, and connect it to an unmarshalling instance.875# This function picks the fastest available XML parser.876#877# return A (parser, unmarshaller) tuple.878 879def getparser(use_datetime=False, use_builtin_types=False):880 """getparser() -> parser, unmarshaller881 882 Create an instance of the fastest available parser, and attach it883 to an unmarshalling object. Return both objects.884 """885 if FastParser and FastUnmarshaller:886 if use_builtin_types:887 mkdatetime = _datetime_type888 mkbytes = base64.decodebytes889 elif use_datetime:890 mkdatetime = _datetime_type891 mkbytes = _binary892 else:893 mkdatetime = _datetime894 mkbytes = _binary895 target = FastUnmarshaller(True, False, mkbytes, mkdatetime, Fault)896 parser = FastParser(target)897 else:898 target = Unmarshaller(use_datetime=use_datetime, use_builtin_types=use_builtin_types)899 if FastParser:900 parser = FastParser(target)901 else:902 parser = ExpatParser(target)903 return parser, target904 905##906# Convert a Python tuple or a Fault instance to an XML-RPC packet.907#908# @def dumps(params, **options)909# @param params A tuple or Fault instance.910# @keyparam methodname If given, create a methodCall request for911# this method name.912# @keyparam methodresponse If given, create a methodResponse packet.913# If used with a tuple, the tuple must be a singleton (that is,914# it must contain exactly one element).915# @keyparam encoding The packet encoding.916# @return A string containing marshalled data.917 918def dumps(params, methodname=None, methodresponse=None, encoding=None,919 allow_none=False):920 """data [,options] -> marshalled data921 922 Convert an argument tuple or a Fault instance to an XML-RPC923 request (or response, if the methodresponse option is used).924 925 In addition to the data object, the following options can be given926 as keyword arguments:927 928 methodname: the method name for a methodCall packet929 930 methodresponse: true to create a methodResponse packet.931 If this option is used with a tuple, the tuple must be932 a singleton (i.e. it can contain only one element).933 934 encoding: the packet encoding (default is UTF-8)935 936 All byte strings in the data structure are assumed to use the937 packet encoding. Unicode strings are automatically converted,938 where necessary.939 """940 941 assert isinstance(params, (tuple, Fault)), "argument must be tuple or Fault instance"942 if isinstance(params, Fault):943 methodresponse = 1944 elif methodresponse and isinstance(params, tuple):945 assert len(params) == 1, "response tuple must be a singleton"946 947 if not encoding:948 encoding = "utf-8"949 950 if FastMarshaller:951 m = FastMarshaller(encoding)952 else:953 m = Marshaller(encoding, allow_none)954 955 data = m.dumps(params)956 957 if encoding != "utf-8":958 xmlheader = "<?xml version='1.0' encoding='%s'?>\n" % str(encoding)959 else:960 xmlheader = "<?xml version='1.0'?>\n" # utf-8 is default961 962 # standard XML-RPC wrappings963 if methodname:964 # a method call965 data = (966 xmlheader,967 "<methodCall>\n"968 "<methodName>", methodname, "</methodName>\n",969 data,970 "</methodCall>\n"971 )972 elif methodresponse:973 # a method response, or a fault structure974 data = (975 xmlheader,976 "<methodResponse>\n",977 data,978 "</methodResponse>\n"979 )980 else:981 return data # return as is982 return "".join(data)983 984##985# Convert an XML-RPC packet to a Python object. If the XML-RPC packet986# represents a fault condition, this function raises a Fault exception.987#988# @param data An XML-RPC packet, given as an 8-bit string.989# @return A tuple containing the unpacked data, and the method name990# (None if not present).991# @see Fault992 993def loads(data, use_datetime=False, use_builtin_types=False):994 """data -> unmarshalled data, method name995 996 Convert an XML-RPC packet to unmarshalled data plus a method997 name (None if not present).998 999 If the XML-RPC packet represents a fault condition, this function1000 raises a Fault exception.1001 """1002 p, u = getparser(use_datetime=use_datetime, use_builtin_types=use_builtin_types)1003 p.feed(data)1004 p.close()1005 return u.close(), u.getmethodname()1006 1007##1008# Encode a string using the gzip content encoding such as specified by the1009# Content-Encoding: gzip1010# in the HTTP header, as described in RFC 19521011#1012# @param data the unencoded data1013# @return the encoded data1014 1015def gzip_encode(data):1016 """data -> gzip encoded data1017 1018 Encode data using the gzip content encoding as described in RFC 19521019 """1020 if not gzip:1021 raise NotImplementedError1022 f = BytesIO()1023 with gzip.GzipFile(mode="wb", fileobj=f, compresslevel=1) as gzf:1024 gzf.write(data)1025 return f.getvalue()1026 1027##1028# Decode a string using the gzip content encoding such as specified by the1029# Content-Encoding: gzip1030# in the HTTP header, as described in RFC 19521031#1032# @param data The encoded data1033# @keyparam max_decode Maximum bytes to decode (20 MiB default), use negative1034# values for unlimited decoding1035# @return the unencoded data1036# @raises ValueError if data is not correctly coded.1037# @raises ValueError if max gzipped payload length exceeded1038 1039def gzip_decode(data, max_decode=20971520):1040 """gzip encoded data -> unencoded data1041 1042 Decode data using the gzip content encoding as described in RFC 19521043 """1044 if not gzip:1045 raise NotImplementedError1046 with gzip.GzipFile(mode="rb", fileobj=BytesIO(data)) as gzf:1047 try:1048 if max_decode < 0: # no limit1049 decoded = gzf.read()1050 else:1051 decoded = gzf.read(max_decode + 1)1052 except OSError:1053 raise ValueError("invalid data")1054 if max_decode >= 0 and len(decoded) > max_decode:1055 raise ValueError("max gzipped payload length exceeded")1056 return decoded1057 1058##1059# Return a decoded file-like object for the gzip encoding1060# as described in RFC 1952.1061#1062# @param response A stream supporting a read() method1063# @return a file-like object that the decoded data can be read() from1064 1065class GzipDecodedResponse(gzip.GzipFile if gzip else object):1066 """a file-like object to decode a response encoded with the gzip1067 method, as described in RFC 1952.1068 """1069 def __init__(self, response):1070 #response doesn't support tell() and read(), required by1071 #GzipFile1072 if not gzip:1073 raise NotImplementedError1074 self.io = BytesIO(response.read())1075 gzip.GzipFile.__init__(self, mode="rb", fileobj=self.io)1076 1077 def close(self):1078 try:1079 gzip.GzipFile.close(self)1080 finally:1081 self.io.close()1082 1083 1084# --------------------------------------------------------------------1085# request dispatcher1086 1087class _Method:1088 # some magic to bind an XML-RPC method to an RPC server.1089 # supports "nested" methods (e.g. examples.getStateName)1090 def __init__(self, send, name):1091 self.__send = send1092 self.__name = name1093 def __getattr__(self, name):1094 return _Method(self.__send, "%s.%s" % (self.__name, name))1095 def __call__(self, *args):1096 return self.__send(self.__name, args)1097 1098##1099# Standard transport class for XML-RPC over HTTP.1100# <p>1101# You can create custom transports by subclassing this method, and1102# overriding selected methods.1103 1104class Transport:1105 """Handles an HTTP transaction to an XML-RPC server."""1106 1107 # client identifier (may be overridden)1108 user_agent = "Python-xmlrpc/%s" % __version__1109 1110 #if true, we'll request gzip encoding1111 accept_gzip_encoding = True1112 1113 # if positive, encode request using gzip if it exceeds this threshold1114 # note that many servers will get confused, so only use it if you know1115 # that they can decode such a request1116 encode_threshold = None #None = don't encode1117 1118 def __init__(self, use_datetime=False, use_builtin_types=False,1119 *, headers=()):1120 self._use_datetime = use_datetime1121 self._use_builtin_types = use_builtin_types1122 self._connection = (None, None)1123 self._headers = list(headers)1124 self._extra_headers = []1125 1126 ##1127 # Send a complete request, and parse the response.1128 # Retry request if a cached connection has disconnected.1129 #1130 # @param host Target host.1131 # @param handler Target PRC handler.1132 # @param request_body XML-RPC request body.1133 # @param verbose Debugging flag.1134 # @return Parsed response.1135 1136 def request(self, host, handler, request_body, verbose=False):1137 #retry request once if cached connection has gone cold1138 for i in (0, 1):1139 try:1140 return self.single_request(host, handler, request_body, verbose)1141 except http.client.RemoteDisconnected:1142 if i:1143 raise1144 except OSError as e:1145 if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED,1146 errno.EPIPE):1147 raise1148 1149 def single_request(self, host, handler, request_body, verbose=False):1150 # issue XML-RPC request1151 try:1152 http_conn = self.send_request(host, handler, request_body, verbose)1153 resp = http_conn.getresponse()1154 if resp.status == 200:1155 self.verbose = verbose1156 return self.parse_response(resp)1157 1158 except Fault:1159 raise1160 except Exception:1161 #All unexpected errors leave connection in1162 # a strange state, so we clear it.1163 self.close()1164 raise1165 1166 #We got an error response.1167 #Discard any response data and raise exception1168 if resp.getheader("content-length", ""):1169 resp.read()1170 raise ProtocolError(1171 host + handler,1172 resp.status, resp.reason,1173 dict(resp.getheaders())1174 )1175 1176 1177 ##1178 # Create parser.1179 #1180 # @return A 2-tuple containing a parser and an unmarshaller.1181 1182 def getparser(self):1183 # get parser and unmarshaller1184 return getparser(use_datetime=self._use_datetime,1185 use_builtin_types=self._use_builtin_types)1186 1187 ##1188 # Get authorization info from host parameter1189 # Host may be a string, or a (host, x509-dict) tuple; if a string,1190 # it is checked for a "user:pw@host" format, and a "Basic1191 # Authentication" header is added if appropriate.1192 #1193 # @param host Host descriptor (URL or (URL, x509 info) tuple).1194 # @return A 3-tuple containing (actual host, extra headers,1195 # x509 info). The header and x509 fields may be None.1196 1197 def get_host_info(self, host):1198 1199 x509 = {}1200 if isinstance(host, tuple):