• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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@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"""
90An XML-RPC client interface for Python.
91
92The marshalling and response parser code can also be used to
93implement XML-RPC servers.
94
95Exported 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
102Exported 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
117Exported constants:
118
119  (none)
120
121Exported 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
131import base64
132import sys
133import time
134from datetime import datetime
135from decimal import Decimal
136import http.client
137import urllib.parse
138from xml.parsers import expat
139import errno
140from io import BytesIO
141try:
142    import gzip
143except ImportError:
144    gzip = None #python can be built without zlib/gzip support
145
146# --------------------------------------------------------------------
147# Internal stuff
148
149def 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
158MAXINT =  2**31-1
159MININT = -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
166PARSE_ERROR       = -32700
167SERVER_ERROR      = -32600
168APPLICATION_ERROR = -32500
169SYSTEM_ERROR      = -32400
170TRANSPORT_ERROR   = -32300
171
172# Specific errors
173NOT_WELLFORMED_ERROR  = -32700
174UNSUPPORTED_ENCODING  = -32701
175INVALID_ENCODING_CHAR = -32702
176INVALID_XMLRPC        = -32600
177METHOD_NOT_FOUND      = -32601
178INVALID_METHOD_PARAMS = -32602
179INTERNAL_ERROR        = -32603
180
181# --------------------------------------------------------------------
182# Exceptions
183
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 HTTP
193# transport layer, if the server returns an error code other than 200
194# (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 = url
206        self.errcode = errcode
207        self.errmsg = errmsg
208        self.headers = headers
209    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 is
217# raised by the unmarshalling layer, if the XML-RPC response is
218# malformed.
219
220class ResponseError(Error):
221    """Indicates a broken response package."""
222    pass
223
224##
225# Indicates an XML-RPC fault response package.  This exception is
226# raised by the unmarshalling layer, if the XML-RPC response contains
227# a fault string.  This exception can also be used as a class, to
228# 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 = faultCode
238        self.faultString = faultString
239    def __repr__(self):
240        return "<%s %s: %r>" % (self.__class__.__name__,
241                                self.faultCode, self.faultString)
242
243# --------------------------------------------------------------------
244# Special values
245
246##
247# Backwards compatibility
248boolean = Boolean = bool
249
250
251def _iso8601_format(value):
252    if value.tzinfo is not None:
253        # XML-RPC only uses the naive portion of the datetime
254        value = value.replace(tzinfo=None)
255    # XML-RPC doesn't use '-' separator in the date part
256    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 or
272    localtime integer value to generate 'dateTime.iso8601' XML-RPC
273    value.
274    """
275
276    def __init__(self, value=0):
277        if isinstance(value, str):
278            self.value = value
279        else:
280            self.value = _strftime(value)
281
282    def make_comparable(self, other):
283        if isinstance(other, DateTime):
284            s = self.value
285            o = other.value
286        elif isinstance(other, datetime):
287            s = self.value
288            o = _iso8601_format(other)
289        elif isinstance(other, str):
290            s = self.value
291            o = other
292        elif hasattr(other, "timetuple"):
293            s = self.timetuple()
294            o = other.timetuple()
295        else:
296            s = self
297            o = NotImplemented
298        return s, o
299
300    def __lt__(self, other):
301        s, o = self.make_comparable(other)
302        if o is NotImplemented:
303            return NotImplemented
304        return s < o
305
306    def __le__(self, other):
307        s, o = self.make_comparable(other)
308        if o is NotImplemented:
309            return NotImplemented
310        return s <= o
311
312    def __gt__(self, other):
313        s, o = self.make_comparable(other)
314        if o is NotImplemented:
315            return NotImplemented
316        return s > o
317
318    def __ge__(self, other):
319        s, o = self.make_comparable(other)
320        if o is NotImplemented:
321            return NotImplemented
322        return s >= o
323
324    def __eq__(self, other):
325        s, o = self.make_comparable(other)
326        if o is NotImplemented:
327            return NotImplemented
328        return s == o
329
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.value
340
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 value
357
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 kind
363# 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 = data
379
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.data
391        return self.data == other
392
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 structure
404    value = Binary()
405    value.decode(data)
406    return value
407
408WRAPPERS = (DateTime, Binary)
409
410# --------------------------------------------------------------------
411# XML parsers
412
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 = target
418        parser.StartElementHandler = target.start
419        parser.EndElementHandler = target.end
420        parser.CharacterDataHandler = target.data
421        encoding = None
422        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._parser
430        except AttributeError:
431            pass
432        else:
433            del self._target, self._parser # get rid of circular references
434            parser.Parse(b"", True) # end of data
435
436# --------------------------------------------------------------------
437# XML-RPC marshalling and unmarshalling code
438
439##
440# XML-RPC marshaller.
441#
442# @param encoding Default encoding for 8-bit strings.  The default
443#     value is None (interpreted as UTF-8).
444# @see dumps
445
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 use
450    the "dumps" method to convert your data (represented as a tuple)
451    to an XML-RPC params chunk.  To write a fault response, pass a
452    Fault instance instead.  You may prefer to use the "dumps" module
453    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 = None
462        self.encoding = encoding
463        self.allow_none = allow_none
464
465    dispatch = {}
466
467    def dumps(self, values):
468        out = []
469        write = out.append
470        dump = self.__dump
471        if isinstance(values, Fault):
472            # fault instance
473            write("<fault>\n")
474            dump({'faultCode': values.faultCode,
475                  'faultString': values.faultString},
476                 write)
477            write("</fault>\n")
478        else:
479            # parameter block
480            # FIXME: the xml-rpc specification allows us to leave out
481            # the entire <params> block if there are no parameters.
482            # however, changing this may break older code (including
483            # old versions of xmlrpclib.py), so this is better left as
484            # is for now.  See @XMLRPC3 for more information. /F
485            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 result
493
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 structure
499            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 types
503            # (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-fix
508            # 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_nil
517
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_bool
523
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_long
531
532    # backward compatible
533    dump_int = dump_long
534
535    def dump_double(self, value, write):
536        write("<value><double>")
537        write(repr(value))
538        write("</double></value>\n")
539    dispatch[float] = dump_double
540
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_unicode
546
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_bytes
553    dispatch[bytearray] = dump_bytes
554
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] = None
560        dump = self.__dump
561        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_array
567    dispatch[list] = dump_array
568
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] = None
574        dump = self.__dump
575        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_struct
586
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_datetime
592
593    def dump_instance(self, value, write):
594        # check for special wrappers
595        if value.__class__ in WRAPPERS:
596            self.write = write
597            value.encode(self)
598            del self.write
599        else:
600            # store instance attributes as a struct (really?)
601            self.dump_struct(value.__dict__, write)
602    dispatch[DateTime] = dump_instance
603    dispatch[Binary] = dump_instance
604    # XXX(twouters): using "_arbitrary_instance" as key as a quick-fix
605    # for the p3yk merge, this should probably be fixed more neatly.
606    dispatch["_arbitrary_instance"] = dump_instance
607
608##
609# XML-RPC unmarshaller.
610#
611# @see loads
612
613class Unmarshaller:
614    """Unmarshal an XML-RPC response, based on incoming XML event
615    messages (start, data, end).  Call close() to get the resulting
616    data structure.
617
618    Note that this reader is fairly tolerant, and gladly accepts bogus
619    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 = None
627        self._stack = []
628        self._marks = []
629        self._data = []
630        self._value = False
631        self._methodname = None
632        self._encoding = "utf-8"
633        self.append = self._stack.append
634        self._use_datetime = use_builtin_types or use_datetime
635        self._use_bytes = use_builtin_types
636
637    def close(self):
638        # return response tuple and target method
639        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._methodname
647
648    #
649    # event handlers
650
651    def xml(self, encoding, standalone):
652        self._encoding = encoding
653        # FIXME: assert standalone == 1 ???
654
655    def start(self, tag, attrs):
656        # prepare to handle this element
657        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 handler
671        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 support
684
685    def end_dispatch(self, tag, data):
686        # dispatch data
687        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 decoders
700
701    dispatch = {}
702
703    def end_nil (self, data):
704        self.append(None)
705        self._value = 0
706    dispatch["nil"] = end_nil
707
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 = 0
716    dispatch["boolean"] = end_boolean
717
718    def end_int(self, data):
719        self.append(int(data))
720        self._value = 0
721    dispatch["i1"] = end_int
722    dispatch["i2"] = end_int
723    dispatch["i4"] = end_int
724    dispatch["i8"] = end_int
725    dispatch["int"] = end_int
726    dispatch["biginteger"] = end_int
727
728    def end_double(self, data):
729        self.append(float(data))
730        self._value = 0
731    dispatch["double"] = end_double
732    dispatch["float"] = end_double
733
734    def end_bigdecimal(self, data):
735        self.append(Decimal(data))
736        self._value = 0
737    dispatch["bigdecimal"] = end_bigdecimal
738
739    def end_string(self, data):
740        if self._encoding:
741            data = data.decode(self._encoding)
742        self.append(data)
743        self._value = 0
744    dispatch["string"] = end_string
745    dispatch["name"] = end_string # struct keys are always strings
746
747    def end_array(self, data):
748        mark = self._marks.pop()
749        # map arrays to Python lists
750        self._stack[mark:] = [self._stack[mark:]]
751        self._value = 0
752    dispatch["array"] = end_array
753
754    def end_struct(self, data):
755        mark = self._marks.pop()
756        # map structs to Python dictionaries
757        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 = 0
763    dispatch["struct"] = end_struct
764
765    def end_base64(self, data):
766        value = Binary()
767        value.decode(data.encode("ascii"))
768        if self._use_bytes:
769            value = value.data
770        self.append(value)
771        self._value = 0
772    dispatch["base64"] = end_base64
773
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_dateTime
781
782    def end_value(self, data):
783        # if we stumble upon a value element with no internal
784        # elements, treat it as a string element
785        if self._value:
786            self.end_string(data)
787    dispatch["value"] = end_value
788
789    def end_params(self, data):
790        self._type = "params"
791    dispatch["params"] = end_params
792
793    def end_fault(self, data):
794        self._type = "fault"
795    dispatch["fault"] = end_fault
796
797    def end_methodName(self, data):
798        if self._encoding:
799            data = data.decode(self._encoding)
800        self._methodname = data
801        self._type = "methodName" # no params
802    dispatch["methodName"] = end_methodName
803
804## Multicall support
805#
806
807class _MultiCallMethod:
808    # some lesser magic to store calls made to a MultiCall object
809    # for batch execution
810    def __init__(self, call_list, name):
811        self.__call_list = call_list
812        self.__name = name
813    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 are
820    raised in response to xmlrpc faults."""
821
822    def __init__(self, results):
823        self.results = results
824
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 calls
836
837    server should be a ServerProxy object.
838
839    Methods can be added to the MultiCall using normal
840    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 = server
853        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 functions
870
871FastMarshaller = FastParser = FastUnmarshaller = None
872
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, unmarshaller
881
882    Create an instance of the fastest available parser, and attach it
883    to an unmarshalling object.  Return both objects.
884    """
885    if FastParser and FastUnmarshaller:
886        if use_builtin_types:
887            mkdatetime = _datetime_type
888            mkbytes = base64.decodebytes
889        elif use_datetime:
890            mkdatetime = _datetime_type
891            mkbytes = _binary
892        else:
893            mkdatetime = _datetime
894            mkbytes = _binary
895        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, target
904
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 for
911#     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 data
921
922    Convert an argument tuple or a Fault instance to an XML-RPC
923    request (or response, if the methodresponse option is used).
924
925    In addition to the data object, the following options can be given
926    as keyword arguments:
927
928        methodname: the method name for a methodCall packet
929
930        methodresponse: true to create a methodResponse packet.
931        If this option is used with a tuple, the tuple must be
932        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 the
937    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 = 1
944    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 default
961
962    # standard XML-RPC wrappings
963    if methodname:
964        # a method call
965        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 structure
974        data = (
975            xmlheader,
976            "<methodResponse>\n",
977            data,
978            "</methodResponse>\n"
979            )
980    else:
981        return data # return as is
982    return "".join(data)
983
984##
985# Convert an XML-RPC packet to a Python object.  If the XML-RPC packet
986# 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 name
990#     (None if not present).
991# @see Fault
992
993def loads(data, use_datetime=False, use_builtin_types=False):
994    """data -> unmarshalled data, method name
995
996    Convert an XML-RPC packet to unmarshalled data plus a method
997    name (None if not present).
998
999    If the XML-RPC packet represents a fault condition, this function
1000    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 the
1009# Content-Encoding: gzip
1010# in the HTTP header, as described in RFC 1952
1011#
1012# @param data the unencoded data
1013# @return the encoded data
1014
1015def gzip_encode(data):
1016    """data -> gzip encoded data
1017
1018    Encode data using the gzip content encoding as described in RFC 1952
1019    """
1020    if not gzip:
1021        raise NotImplementedError
1022    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 the
1029# Content-Encoding: gzip
1030# in the HTTP header, as described in RFC 1952
1031#
1032# @param data The encoded data
1033# @keyparam max_decode Maximum bytes to decode (20 MiB default), use negative
1034#    values for unlimited decoding
1035# @return the unencoded data
1036# @raises ValueError if data is not correctly coded.
1037# @raises ValueError if max gzipped payload length exceeded
1038
1039def gzip_decode(data, max_decode=20971520):
1040    """gzip encoded data -> unencoded data
1041
1042    Decode data using the gzip content encoding as described in RFC 1952
1043    """
1044    if not gzip:
1045        raise NotImplementedError
1046    with gzip.GzipFile(mode="rb", fileobj=BytesIO(data)) as gzf:
1047        try:
1048            if max_decode < 0: # no limit
1049                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 decoded
1057
1058##
1059# Return a decoded file-like object for the gzip encoding
1060# as described in RFC 1952.
1061#
1062# @param response A stream supporting a read() method
1063# @return a file-like object that the decoded data can be read() from
1064
1065class GzipDecodedResponse(gzip.GzipFile if gzip else object):
1066    """a file-like object to decode a response encoded with the gzip
1067    method, as described in RFC 1952.
1068    """
1069    def __init__(self, response):
1070        #response doesn't support tell() and read(), required by
1071        #GzipFile
1072        if not gzip:
1073            raise NotImplementedError
1074        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 dispatcher
1086
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 = send
1092        self.__name = name
1093    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, and
1102# 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 encoding
1111    accept_gzip_encoding = True
1112
1113    # if positive, encode request using gzip if it exceeds this threshold
1114    # note that many servers will get confused, so only use it if you know
1115    # that they can decode such a request
1116    encode_threshold = None #None = don't encode
1117
1118    def __init__(self, use_datetime=False, use_builtin_types=False,
1119                 *, headers=()):
1120        self._use_datetime = use_datetime
1121        self._use_builtin_types = use_builtin_types
1122        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 cold
1138        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                    raise
1144            except OSError as e:
1145                if i or e.errno not in (errno.ECONNRESET, errno.ECONNABORTED,
1146                                        errno.EPIPE):
1147                    raise
1148
1149    def single_request(self, host, handler, request_body, verbose=False):
1150        # issue XML-RPC request
1151        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 = verbose
1156                return self.parse_response(resp)
1157
1158        except Fault:
1159            raise
1160        except Exception:
1161            #All unexpected errors leave connection in
1162            # a strange state, so we clear it.
1163            self.close()
1164            raise
1165
1166        #We got an error response.
1167        #Discard any response data and raise exception
1168        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 unmarshaller
1184        return getparser(use_datetime=self._use_datetime,
1185                         use_builtin_types=self._use_builtin_types)
1186
1187    ##
1188    # Get authorization info from host parameter
1189    # 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 "Basic
1191    # 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):
1201            host, x509 = host
1202
1203        auth, host = urllib.parse._splituser(host)
1204
1205        if auth:
1206            auth = urllib.parse.unquote_to_bytes(auth)
1207            auth = base64.encodebytes(auth).decode("utf-8")
1208            auth = "".join(auth.split()) # get rid of whitespace
1209            extra_headers = [
1210                ("Authorization", "Basic " + auth)
1211                ]
1212        else:
1213            extra_headers = []
1214
1215        return host, extra_headers, x509
1216
1217    ##
1218    # Connect to server.
1219    #
1220    # @param host Target host.
1221    # @return An HTTPConnection object
1222
1223    def make_connection(self, host):
1224        #return an existing connection if possible.  This allows
1225        #HTTP/1.1 keep-alive.
1226        if self._connection and host == self._connection[0]:
1227            return self._connection[1]
1228        # create a HTTP connection object from a host descriptor
1229        chost, self._extra_headers, x509 = self.get_host_info(host)
1230        self._connection = host, http.client.HTTPConnection(chost)
1231        return self._connection[1]
1232
1233    ##
1234    # Clear any cached connection object.
1235    # Used in the event of socket errors.
1236    #
1237    def close(self):
1238        host, connection = self._connection
1239        if connection:
1240            self._connection = (None, None)
1241            connection.close()
1242
1243    ##
1244    # Send HTTP request.
1245    #
1246    # @param host Host descriptor (URL or (URL, x509 info) tuple).
1247    # @param handler Target RPC handler (a path relative to host)
1248    # @param request_body The XML-RPC request body
1249    # @param debug Enable debugging if debug is true.
1250    # @return An HTTPConnection.
1251
1252    def send_request(self, host, handler, request_body, debug):
1253        connection = self.make_connection(host)
1254        headers = self._headers + self._extra_headers
1255        if debug:
1256            connection.set_debuglevel(1)
1257        if self.accept_gzip_encoding and gzip:
1258            connection.putrequest("POST", handler, skip_accept_encoding=True)
1259            headers.append(("Accept-Encoding", "gzip"))
1260        else:
1261            connection.putrequest("POST", handler)
1262        headers.append(("Content-Type", "text/xml"))
1263        headers.append(("User-Agent", self.user_agent))
1264        self.send_headers(connection, headers)
1265        self.send_content(connection, request_body)
1266        return connection
1267
1268    ##
1269    # Send request headers.
1270    # This function provides a useful hook for subclassing
1271    #
1272    # @param connection httpConnection.
1273    # @param headers list of key,value pairs for HTTP headers
1274
1275    def send_headers(self, connection, headers):
1276        for key, val in headers:
1277            connection.putheader(key, val)
1278
1279    ##
1280    # Send request body.
1281    # This function provides a useful hook for subclassing
1282    #
1283    # @param connection httpConnection.
1284    # @param request_body XML-RPC request body.
1285
1286    def send_content(self, connection, request_body):
1287        #optionally encode the request
1288        if (self.encode_threshold is not None and
1289            self.encode_threshold < len(request_body) and
1290            gzip):
1291            connection.putheader("Content-Encoding", "gzip")
1292            request_body = gzip_encode(request_body)
1293
1294        connection.putheader("Content-Length", str(len(request_body)))
1295        connection.endheaders(request_body)
1296
1297    ##
1298    # Parse response.
1299    #
1300    # @param file Stream.
1301    # @return Response tuple and target method.
1302
1303    def parse_response(self, response):
1304        # read response data from httpresponse, and parse it
1305        # Check for new http response object, otherwise it is a file object.
1306        if hasattr(response, 'getheader'):
1307            if response.getheader("Content-Encoding", "") == "gzip":
1308                stream = GzipDecodedResponse(response)
1309            else:
1310                stream = response
1311        else:
1312            stream = response
1313
1314        p, u = self.getparser()
1315
1316        while data := stream.read(1024):
1317            if self.verbose:
1318                print("body:", repr(data))
1319            p.feed(data)
1320
1321        if stream is not response:
1322            stream.close()
1323        p.close()
1324
1325        return u.close()
1326
1327##
1328# Standard transport class for XML-RPC over HTTPS.
1329
1330class SafeTransport(Transport):
1331    """Handles an HTTPS transaction to an XML-RPC server."""
1332
1333    def __init__(self, use_datetime=False, use_builtin_types=False,
1334                 *, headers=(), context=None):
1335        super().__init__(use_datetime=use_datetime,
1336                         use_builtin_types=use_builtin_types,
1337                         headers=headers)
1338        self.context = context
1339
1340    # FIXME: mostly untested
1341
1342    def make_connection(self, host):
1343        if self._connection and host == self._connection[0]:
1344            return self._connection[1]
1345
1346        if not hasattr(http.client, "HTTPSConnection"):
1347            raise NotImplementedError(
1348            "your version of http.client doesn't support HTTPS")
1349        # create a HTTPS connection object from a host descriptor
1350        # host may be a string, or a (host, x509-dict) tuple
1351        chost, self._extra_headers, x509 = self.get_host_info(host)
1352        self._connection = host, http.client.HTTPSConnection(chost,
1353            None, context=self.context, **(x509 or {}))
1354        return self._connection[1]
1355
1356##
1357# Standard server proxy.  This class establishes a virtual connection
1358# to an XML-RPC server.
1359# <p>
1360# This class is available as ServerProxy and Server.  New code should
1361# use ServerProxy, to avoid confusion.
1362#
1363# @def ServerProxy(uri, **options)
1364# @param uri The connection point on the server.
1365# @keyparam transport A transport factory, compatible with the
1366#    standard transport class.
1367# @keyparam encoding The default encoding used for 8-bit strings
1368#    (default is UTF-8).
1369# @keyparam verbose Use a true value to enable debugging output.
1370#    (printed to standard output).
1371# @see Transport
1372
1373class ServerProxy:
1374    """uri [,options] -> a logical connection to an XML-RPC server
1375
1376    uri is the connection point on the server, given as
1377    scheme://host/target.
1378
1379    The standard implementation always supports the "http" scheme.  If
1380    SSL socket support is available (Python 2.0), it also supports
1381    "https".
1382
1383    If the target part and the slash preceding it are both omitted,
1384    "/RPC2" is assumed.
1385
1386    The following options can be given as keyword arguments:
1387
1388        transport: a transport factory
1389        encoding: the request encoding (default is UTF-8)
1390
1391    All 8-bit strings passed to the server proxy are assumed to use
1392    the given encoding.
1393    """
1394
1395    def __init__(self, uri, transport=None, encoding=None, verbose=False,
1396                 allow_none=False, use_datetime=False, use_builtin_types=False,
1397                 *, headers=(), context=None):
1398        # establish a "logical" server connection
1399
1400        # get the url
1401        p = urllib.parse.urlsplit(uri)
1402        if p.scheme not in ("http", "https"):
1403            raise OSError("unsupported XML-RPC protocol")
1404        self.__host = p.netloc
1405        self.__handler = urllib.parse.urlunsplit(["", "", *p[2:]])
1406        if not self.__handler:
1407            self.__handler = "/RPC2"
1408
1409        if transport is None:
1410            if p.scheme == "https":
1411                handler = SafeTransport
1412                extra_kwargs = {"context": context}
1413            else:
1414                handler = Transport
1415                extra_kwargs = {}
1416            transport = handler(use_datetime=use_datetime,
1417                                use_builtin_types=use_builtin_types,
1418                                headers=headers,
1419                                **extra_kwargs)
1420        self.__transport = transport
1421
1422        self.__encoding = encoding or 'utf-8'
1423        self.__verbose = verbose
1424        self.__allow_none = allow_none
1425
1426    def __close(self):
1427        self.__transport.close()
1428
1429    def __request(self, methodname, params):
1430        # call a method on the remote server
1431
1432        request = dumps(params, methodname, encoding=self.__encoding,
1433                        allow_none=self.__allow_none).encode(self.__encoding, 'xmlcharrefreplace')
1434
1435        response = self.__transport.request(
1436            self.__host,
1437            self.__handler,
1438            request,
1439            verbose=self.__verbose
1440            )
1441
1442        if len(response) == 1:
1443            response = response[0]
1444
1445        return response
1446
1447    def __repr__(self):
1448        return (
1449            "<%s for %s%s>" %
1450            (self.__class__.__name__, self.__host, self.__handler)
1451            )
1452
1453    def __getattr__(self, name):
1454        # magic method dispatcher
1455        return _Method(self.__request, name)
1456
1457    # note: to call a remote object with a non-standard name, use
1458    # result getattr(server, "strange-python-name")(args)
1459
1460    def __call__(self, attr):
1461        """A workaround to get special attributes on the ServerProxy
1462           without interfering with the magic __getattr__
1463        """
1464        if attr == "close":
1465            return self.__close
1466        elif attr == "transport":
1467            return self.__transport
1468        raise AttributeError("Attribute %r not found" % (attr,))
1469
1470    def __enter__(self):
1471        return self
1472
1473    def __exit__(self, *args):
1474        self.__close()
1475
1476# compatibility
1477
1478Server = ServerProxy
1479
1480# --------------------------------------------------------------------
1481# test code
1482
1483if __name__ == "__main__":
1484
1485    # simple test program (from the XML-RPC specification)
1486
1487    # local server, available from Lib/xmlrpc/server.py
1488    server = ServerProxy("http://localhost:8000")
1489
1490    try:
1491        print(server.currentTime.getCurrentTime())
1492    except Error as v:
1493        print("ERROR", v)
1494
1495    multi = MultiCall(server)
1496    multi.getData()
1497    multi.pow(2,9)
1498    multi.add(1,2)
1499    try:
1500        for response in multi():
1501            print(response)
1502    except Error as v:
1503        print("ERROR", v)
1504