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