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