Mercurial > repos > rliterman > csp2
comparison CSP2/CSP2_env/env-d9b9114564458d9d-741b3de822f2aaca6c6caa4325c4afce/lib/python3.8/xmlrpc/client.py @ 68:5028fdace37b
planemo upload commit 2e9511a184a1ca667c7be0c6321a36dc4e3d116d
author | jpayne |
---|---|
date | Tue, 18 Mar 2025 16:23:26 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
67:0e9998148a16 | 68:5028fdace37b |
---|---|
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 """ | |
90 An XML-RPC client interface for Python. | |
91 | |
92 The marshalling and response parser code can also be used to | |
93 implement XML-RPC servers. | |
94 | |
95 Exported exceptions: | |
96 | |
97 Error Base class for client errors | |
98 ProtocolError Indicates an HTTP protocol error | |
99 ResponseError Indicates a broken response package | |
100 Fault Indicates an XML-RPC fault package | |
101 | |
102 Exported classes: | |
103 | |
104 ServerProxy Represents a logical connection to an XML-RPC server | |
105 | |
106 MultiCall Executor of boxcared xmlrpc requests | |
107 DateTime dateTime wrapper for an ISO 8601 string or time tuple or | |
108 localtime integer value to generate a "dateTime.iso8601" | |
109 XML-RPC value | |
110 Binary binary data wrapper | |
111 | |
112 Marshaller Generate an XML-RPC params chunk from a Python data structure | |
113 Unmarshaller Unmarshal an XML-RPC response from incoming XML event message | |
114 Transport Handles an HTTP transaction to an XML-RPC server | |
115 SafeTransport Handles an HTTPS transaction to an XML-RPC server | |
116 | |
117 Exported constants: | |
118 | |
119 (none) | |
120 | |
121 Exported functions: | |
122 | |
123 getparser Create instance of the fastest available parser & attach | |
124 to an unmarshalling object | |
125 dumps Convert an argument tuple or a Fault instance to an XML-RPC | |
126 request (or response, if the methodresponse option is used). | |
127 loads Convert an XML-RPC packet to unmarshalled data plus a method | |
128 name (None if not present). | |
129 """ | |
130 | |
131 import base64 | |
132 import sys | |
133 import time | |
134 from datetime import datetime | |
135 from decimal import Decimal | |
136 import http.client | |
137 import urllib.parse | |
138 from xml.parsers import expat | |
139 import errno | |
140 from io import BytesIO | |
141 try: | |
142 import gzip | |
143 except ImportError: | |
144 gzip = None #python can be built without zlib/gzip support | |
145 | |
146 # -------------------------------------------------------------------- | |
147 # Internal stuff | |
148 | |
149 def escape(s): | |
150 s = s.replace("&", "&") | |
151 s = s.replace("<", "<") | |
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 | |
158 MAXINT = 2**31-1 | |
159 MININT = -2**31 | |
160 | |
161 # -------------------------------------------------------------------- | |
162 # Error constants (from Dan Libby's specification at | |
163 # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php) | |
164 | |
165 # Ranges of errors | |
166 PARSE_ERROR = -32700 | |
167 SERVER_ERROR = -32600 | |
168 APPLICATION_ERROR = -32500 | |
169 SYSTEM_ERROR = -32400 | |
170 TRANSPORT_ERROR = -32300 | |
171 | |
172 # Specific errors | |
173 NOT_WELLFORMED_ERROR = -32700 | |
174 UNSUPPORTED_ENCODING = -32701 | |
175 INVALID_ENCODING_CHAR = -32702 | |
176 INVALID_XMLRPC = -32600 | |
177 METHOD_NOT_FOUND = -32601 | |
178 INVALID_METHOD_PARAMS = -32602 | |
179 INTERNAL_ERROR = -32603 | |
180 | |
181 # -------------------------------------------------------------------- | |
182 # Exceptions | |
183 | |
184 ## | |
185 # Base class for all kinds of client-side errors. | |
186 | |
187 class Error(Exception): | |
188 """Base class for client errors.""" | |
189 __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 | |
201 class 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 | |
220 class 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 | |
233 class 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 | |
249 boolean = 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) | |
267 if _day0.strftime('%Y') == '0001': # Mac OS X | |
268 def _iso8601_format(value): | |
269 return value.strftime("%Y%m%dT%H:%M:%S") | |
270 elif _day0.strftime('%4Y') == '0001': # Linux | |
271 def _iso8601_format(value): | |
272 return value.strftime("%4Y%m%dT%H:%M:%S") | |
273 else: | |
274 def _iso8601_format(value): | |
275 return value.strftime("%Y%m%dT%H:%M:%S").zfill(17) | |
276 del _day0 | |
277 | |
278 | |
279 def _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 | |
290 class 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 | |
365 def _datetime(data): | |
366 # decode xml element contents into a DateTime structure. | |
367 value = DateTime() | |
368 value.decode(data) | |
369 return value | |
370 | |
371 def _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 | |
380 class 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 | |
415 def _binary(data): | |
416 # decode xml element contents into a Binary structure | |
417 value = Binary() | |
418 value.decode(data) | |
419 return value | |
420 | |
421 WRAPPERS = (DateTime, Binary) | |
422 | |
423 # -------------------------------------------------------------------- | |
424 # XML parsers | |
425 | |
426 class 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 | |
459 class 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 | |
626 class 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 | |
820 class _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 | |
831 class 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 | |
847 class 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 | |
884 FastMarshaller = 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 | |
892 def 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 | |
931 def 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 | |
1006 def 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 | |
1028 def 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 | |
1052 def 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 | |
1078 class 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 | |
1100 class _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 | |
1117 class 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 | |
1346 class 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 | |
1389 class 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 | |
1493 Server = ServerProxy | |
1494 | |
1495 # -------------------------------------------------------------------- | |
1496 # test code | |
1497 | |
1498 if __name__ == "__main__": | |
1499 | |
1500 # simple test program (from the XML-RPC specification) | |
1501 | |
1502 # local server, available from Lib/xmlrpc/server.py | |
1503 server = ServerProxy("http://localhost:8000") | |
1504 | |
1505 try: | |
1506 print(server.currentTime.getCurrentTime()) | |
1507 except Error as v: | |
1508 print("ERROR", v) | |
1509 | |
1510 multi = MultiCall(server) | |
1511 multi.getData() | |
1512 multi.pow(2,9) | |
1513 multi.add(1,2) | |
1514 try: | |
1515 for response in multi(): | |
1516 print(response) | |
1517 except Error as v: | |
1518 print("ERROR", v) |