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("<", "<") 152 return s.replace(">", ">",) 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