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