1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 __version__ = "$Revision$"
19
20 import socket
21 import re
22 import sys
23
24 from rhn import transports
25 from rhn.i18n import sstr
26 from rhn.UserDictCase import UserDictCase
27
28 try:
29 import xmlrpclib
30 from types import ListType, TupleType, StringType, UnicodeType, DictType, DictionaryType
31 from urllib import splittype, splithost
32 except ImportError:
33 import xmlrpc.client as xmlrpclib
34 ListType = list
35 TupleType = tuple
36 StringType = bytes
37 UnicodeType = str
38 DictType = dict
39 DictionaryType = dict
40 from urllib.parse import splittype, splithost
41
42
43
44 MAX_REDIRECTIONS = 5
45
47 """ Returns true if n is IPv6 address, false otherwise. """
48 try:
49 socket.inet_pton(socket.AF_INET6, n)
50 return True
51 except:
52 return False
53
55 """ Function used to split host information in an URL per RFC 2396
56 handle full hostname like user:passwd@host:port
57 """
58 l = hoststring.split('@', 1)
59 host = None
60 port = None
61 user = None
62 passwd = None
63
64 if len(l) == 2:
65 hostport = l[1]
66
67 userinfo = l[0].split(':', 1)
68 user = userinfo[0]
69 if len(userinfo) == 2:
70 passwd = userinfo[1]
71 else:
72 hostport = l[0]
73
74
75 if hostport[0] == '[':
76
77 host, port = re.split('(?<=\]):', hostport, 1)
78 host = host.lstrip('[').rstrip(']')
79 elif check_ipv6(hostport):
80
81 host = hostport
82 else:
83
84 arr = hostport.split(':', 1)
85 host = arr[0]
86 if len(arr) == 2:
87 port = arr[1]
88
89 return (host, port, user, passwd)
90
92 if proxy == None:
93 raise ValueError("Host string cannot be null")
94
95 arr = proxy.split('://', 1)
96 if len(arr) == 2:
97
98 proxy = arr[1]
99
100 return split_host(proxy)
101
102
105
106
107
109 """uri [,options] -> a logical connection to an XML-RPC server
110
111 uri is the connection point on the server, given as
112 scheme://host/target.
113
114 The standard implementation always supports the "http" scheme. If
115 SSL socket support is available (Python 2.0), it also supports
116 "https".
117
118 If the target part and the slash preceding it are both omitted,
119 "/RPC2" is assumed.
120
121 The following options can be given as keyword arguments:
122
123 transport: a transport factory
124 encoding: the request encoding (default is UTF-8)
125 verbose: verbosity level
126 proxy: use an HTTP proxy
127 username: username for authenticated HTTP proxy
128 password: password for authenticated HTTP proxy
129
130 All 8-bit strings passed to the server proxy are assumed to use
131 the given encoding.
132 """
133
134
135 _transport_class = transports.Transport
136 _transport_class_https = transports.SafeTransport
137 _transport_class_proxy = transports.ProxyTransport
138 _transport_class_https_proxy = transports.SafeProxyTransport
139 - def __init__(self, uri, transport=None, encoding=None, verbose=0,
140 proxy=None, username=None, password=None, refreshCallback=None,
141 progressCallback=None, timeout=None):
142
143
144
145
146
147 if proxy != None:
148 (ph, pp, pu, pw) = get_proxy_info(proxy)
149
150 if pp is not None:
151 proxy = "%s:%s" % (ph, pp)
152 else:
153 proxy = ph
154
155
156
157 if pu is not None and username is None:
158 username = pu
159
160 if pw is not None and password is None:
161 password = pw
162
163 self._uri = sstr(uri)
164 self._refreshCallback = None
165 self._progressCallback = None
166 self._bufferSize = None
167 self._proxy = proxy
168 self._username = username
169 self._password = password
170 self._timeout = timeout
171
172 if len(__version__.split()) > 1:
173 self.rpc_version = __version__.split()[1]
174 else:
175 self.rpc_version = __version__
176
177 self._reset_host_handler_and_type()
178
179 if transport is None:
180 self._allow_redirect = 1
181 transport = self.default_transport(self._type, proxy, username,
182 password, timeout)
183 else:
184
185
186
187
188 self._allow_redirect = 0
189
190 self._redirected = None
191 self.use_handler_path = 1
192 self._transport = transport
193
194 self._trusted_cert_files = []
195 self._lang = None
196
197 self._encoding = encoding
198 self._verbose = verbose
199
200 self.set_refresh_callback(refreshCallback)
201 self.set_progress_callback(progressCallback)
202
203
204 self.send_handler=None
205
206 self._headers = UserDictCase()
207
208 - def default_transport(self, type, proxy=None, username=None, password=None,
209 timeout=None):
210 if proxy:
211 if type == 'https':
212 transport = self._transport_class_https_proxy(proxy,
213 proxyUsername=username, proxyPassword=password, timeout=timeout)
214 else:
215 transport = self._transport_class_proxy(proxy,
216 proxyUsername=username, proxyPassword=password, timeout=timeout)
217 else:
218 if type == 'https':
219 transport = self._transport_class_https(timeout=timeout)
220 else:
221 transport = self._transport_class(timeout=timeout)
222 return transport
223
225 self._allow_redirect = allow
226
228 if not self._allow_redirect:
229 return None
230 return self._redirected
231
233 self._refreshCallback = refreshCallback
234 self._transport.set_refresh_callback(refreshCallback)
235
237 self._bufferSize = bufferSize
238 self._transport.set_buffer_size(bufferSize)
239
241 self._progressCallback = progressCallback
242 self._transport.set_progress_callback(progressCallback, bufferSize)
243
244 - def _req_body(self, params, methodname):
245 return xmlrpclib.dumps(params, methodname, encoding=self._encoding)
246
248 if self._transport:
249 return self._transport.headers_in
250 return None
251
253 if self._transport:
254 return self._transport.response_status
255 return None
256
258 if self._transport:
259 return self._transport.response_reason
260 return None
261
263 """Returns a dictionary with three values:
264 length: the total length of the entity-body (can be None)
265 first_byte_pos: the position of the first byte (zero based)
266 last_byte_pos: the position of the last byte (zero based)
267 The range is inclusive; that is, a response 8-9/102 means two bytes
268 """
269 headers = self.get_response_headers()
270 if not headers:
271 return None
272 content_range = headers.get('Content-Range')
273 if not content_range:
274 return None
275 arr = filter(None, content_range.split())
276 assert arr[0] == "bytes"
277 assert len(arr) == 2
278 arr = arr[1].split('/')
279 assert len(arr) == 2
280
281 brange, total_len = arr
282 if total_len == '*':
283
284
285 total_len = None
286 else:
287 total_len = int(total_len)
288
289 start, end = brange.split('-')
290 result = {
291 'length' : total_len,
292 'first_byte_pos' : int(start),
293 'last_byte_pos' : int(end),
294 }
295 return result
296
298 headers = self.get_response_headers()
299 if not headers:
300 return None
301 if 'Accept-Ranges' in headers:
302 return headers['Accept-Ranges']
303 return None
304
306 """ Reset the attributes:
307 self._host, self._handler, self._type
308 according the value of self._uri.
309 """
310
311 type, uri = splittype(self._uri)
312 if type is None:
313 raise MalformedURIError("missing protocol in uri")
314
315
316 if len(uri) < 3 or uri[0:2] != "//":
317 raise MalformedURIError
318 self._type = type.lower()
319 if self._type not in ("http", "https"):
320 raise IOError("unsupported XML-RPC protocol")
321 self._host, self._handler = splithost(uri)
322 if not self._handler:
323 self._handler = "/RPC2"
324
326 """ Strip characters, which are not allowed according:
327 http://www.w3.org/TR/2006/REC-xml-20060816/#charsets
328 From spec:
329 Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */
330 """
331 regexp = r'[\x00-\x09]|[\x0b-\x0c]|[\x0e-\x1f]'
332 result=[]
333 for item in args:
334 item_type = type(item)
335 if item_type == StringType or item_type == UnicodeType:
336 item = re.sub(regexp, '', sstr(item))
337 elif item_type == TupleType:
338 item = tuple(self._strip_characters(i) for i in item)
339 elif item_type == ListType:
340 item = [self._strip_characters(i) for i in item]
341 elif item_type == DictType or item_type == DictionaryType:
342 item = dict([(self._strip_characters(name, val)) for name, val in item.items()])
343
344
345 result.append(item)
346 if len(result) == 1:
347 return result[0]
348 else:
349 return tuple(result)
350
351 - def _request(self, methodname, params):
352 """ Call a method on the remote server
353 we can handle redirections. """
354
355 redirect_response = 0
356 retry = 0
357
358 self._reset_host_handler_and_type()
359
360 while 1:
361 if retry >= MAX_REDIRECTIONS:
362 raise InvalidRedirectionError(
363 "Unable to fetch requested Package")
364
365
366 self._transport.clear_headers()
367 for k, v in self._headers.items():
368 self._transport.set_header(k, v)
369
370 self._transport.add_header("X-Info",
371 'RPC Processor (C) Red Hat, Inc (version %s)' %
372 self.rpc_version)
373
374 self._transport.set_header("X-Client-Version", 1)
375
376 if self._allow_redirect:
377
378
379 self._transport.add_header("X-RHN-Transport-Capability",
380 "follow-redirects=3")
381
382 if redirect_response:
383 self._transport.add_header('X-RHN-Redirect', '0')
384 if self.send_handler:
385 self._transport.add_header('X-RHN-Path', self.send_handler)
386
387 request = self._req_body(self._strip_characters(params), methodname)
388
389 try:
390 response = self._transport.request(self._host, \
391 self._handler, request, verbose=self._verbose)
392 save_response = self._transport.response_status
393 except xmlrpclib.ProtocolError:
394 if self.use_handler_path:
395 raise
396 else:
397 save_response = sys.exc_info()[1].errcode
398
399 self._redirected = None
400 retry += 1
401 if save_response == 200:
402
403 break
404 elif save_response not in (301, 302):
405
406 self.use_handler_path = 1
407 continue
408
409 self._redirected = self._transport.redirected()
410 self.use_handler_path = 0
411 redirect_response = 1
412
413 if not self._allow_redirect:
414 raise InvalidRedirectionError("Redirects not allowed")
415
416 if self._verbose:
417 print("%s redirected to %s" % (self._uri, self._redirected))
418
419 typ, uri = splittype(self._redirected)
420
421 if typ != None:
422 typ = typ.lower()
423 if typ not in ("http", "https"):
424 raise InvalidRedirectionError(
425 "Redirected to unsupported protocol %s" % typ)
426
427
428
429
430
431
432 if self._type == "https" and typ == "http":
433 raise InvalidRedirectionError(
434 "HTTPS redirected to HTTP is not supported")
435
436 self._host, self._handler = splithost(uri)
437 if not self._handler:
438 self._handler = "/RPC2"
439
440
441
442 del self._transport
443 self._transport = self.default_transport(typ, self._proxy,
444 self._username, self._password, self._timeout)
445 self.set_progress_callback(self._progressCallback)
446 self.set_refresh_callback(self._refreshCallback)
447 self.set_buffer_size(self._bufferSize)
448 self.setlang(self._lang)
449
450 if self._trusted_cert_files != [] and \
451 hasattr(self._transport, "add_trusted_cert"):
452 for certfile in self._trusted_cert_files:
453 self._transport.add_trusted_cert(certfile)
454
455
456 if isinstance(response, transports.File):
457
458 return response
459
460
461 if isinstance(response, TupleType) and len(response) == 1:
462 response = response[0]
463
464 return response
465
467 return (
468 "<%s for %s%s>" %
469 (self.__class__.__name__, self._host, self._handler)
470 )
471
472 __str__ = __repr__
473
477
478
479
480
482 if not self._transport:
483
484 return
485 kwargs.update({
486 'transfer' : transfer,
487 'encoding' : encoding,
488 })
489 self._transport.set_transport_flags(**kwargs)
490
496
500
501
503 if type(arg) in [ type([]), type(()) ]:
504
505 self._headers[name] = [str(a) for a in arg]
506 else:
507 self._headers[name] = str(arg)
508
510 if name in self._headers:
511 vlist = self._headers[name]
512 if not isinstance(vlist, ListType):
513 vlist = [ vlist ]
514 else:
515 vlist = self._headers[name] = []
516 vlist.append(str(arg))
517
518
520 self._lang = lang
521 if self._transport and hasattr(self._transport, "setlang"):
522 self._transport.setlang(lang)
523
524
526 raise NotImplementedError("This method is deprecated")
527
529 self._trusted_cert_files.append(certfile)
530 if self._transport and hasattr(self._transport, "add_trusted_cert"):
531 self._transport.add_trusted_cert(certfile)
532
534 if self._transport:
535 self._transport.close()
536 self._transport = None
537
538
540 - def __init__(self, uri, transport=None, proxy=None, username=None,
541 password=None, client_version=2, headers={}, refreshCallback=None,
542 progressCallback=None, timeout=None):
543 Server.__init__(self, uri,
544 proxy=proxy,
545 username=username,
546 password=password,
547 transport=transport,
548 refreshCallback=refreshCallback,
549 progressCallback=progressCallback,
550 timeout=timeout)
551 self._client_version = client_version
552 self._headers = headers
553
554 self._orig_handler = self._handler
555
556 self.set_range(offset=None, amount=None)
557
558 - def _req_body(self, params, methodname):
559 if not params or len(params) < 1:
560 raise Exception("Required parameter channel not found")
561
562 h_comps = filter(lambda x: x != '', self._orig_handler.split('/'))
563
564 hndl = h_comps + ["$RHN", params[0], methodname] + list(params[1:])
565 self._handler = '/' + '/'.join(hndl)
566
567
568 self.send_handler = self._handler
569
570
571
572 if self._redirected and not self.use_handler_path:
573 self._handler = self._new_req_body()
574
575 for h, v in self._headers.items():
576 self._transport.set_header(h, v)
577
578 if self._offset is not None:
579 if self._offset >= 0:
580 brange = str(self._offset) + '-'
581 if self._amount is not None:
582 brange = brange + str(self._offset + self._amount - 1)
583 else:
584
585
586 brange = '-' + str(-self._offset)
587
588 self._transport.set_header('Range', "bytes=" + brange)
589
590 self._transport.set_transport_flags(allow_partial_content=1)
591
592 return ""
593
594 - def _new_req_body(self):
595 type, tmpuri = splittype(self._redirected)
596 site, handler = splithost(tmpuri)
597 return handler
598
599 - def set_range(self, offset=None, amount=None):
600 if offset is not None:
601 try:
602 offset = int(offset)
603 except ValueError:
604
605 raise RangeError("Invalid value `%s' for offset" % offset, None, sys.exc_info()[2])
606
607 if amount is not None:
608 try:
609 amount = int(amount)
610 except ValueError:
611
612 raise RangeError("Invalid value `%s' for amount" % amount, None, sys.exc_info()[2])
613
614 if amount <= 0:
615 raise RangeError("Invalid value `%s' for amount" % amount)
616
617 self._amount = amount
618 self._offset = offset
619
622
626
627 - def default_transport(self, type, proxy=None, username=None, password=None,
628 timeout=None):
632
635
638
640 import mimetools
641 if not isinstance(headers, mimetools.Message):
642 if name in headers:
643 return [headers[name]]
644 return []
645
646 return [x.split(':', 1)[1].strip() for x in
647 headers.getallmatchingheaders(name)]
648
650 """ some magic to bind an XML-RPC method to an RPC server.
651 supports "nested" methods (e.g. examples.getStateName)
652 """
654 self._send = send
655 self._name = name
657 return _Method(self._send, "%s.%s" % (self._name, name))
659 return self._send(self._name, args)
661 return (
662 "<%s %s (%s)>" %
663 (self.__class__.__name__, self._name, self._send)
664 )
665 __str__ = __repr__
666
667
669 """
670 A "slicing method" allows for byte range requests
671 """
678 self._offset = kwargs.get('offset')
679 self._amount = kwargs.get('amount')
680
681
682 try:
683 self._send.im_self.set_range(offset=self._offset,
684 amount=self._amount)
685 except AttributeError:
686 pass
687
688 result = self._send(self._name, args)
689
690
691 try:
692 self._send.im_self.reset_transport_flags()
693 except AttributeError:
694 pass
695
696 return result
697
698
700 """ Reports the error from the headers. """
701 errcode = 0
702 errmsg = ""
703 s = "X-RHN-Fault-Code"
704 if s in headers:
705 errcode = int(headers[s])
706 s = "X-RHN-Fault-String"
707 if s in headers:
708 _sList = getHeaderValues(headers, s)
709 if _sList:
710 _s = ''.join(_sList)
711 import base64
712 errmsg = "%s" % base64.decodestring(_s)
713
714 return errcode, errmsg
715