Package rhn :: Module transports
[hide private]
[frames] | no frames]

Source Code for Module rhn.transports

  1  # 
  2  # Helper transport objects 
  3  # 
  4  # Copyright (c) 2002--2020 Red Hat, Inc. 
  5  # 
  6  # Author: Mihai Ibanescu <misa@redhat.com> 
  7  # Based on what was previously shipped as cgiwrap: 
  8  #   - Cristian Gafton <gafton@redhat.com> 
  9  #   - Erik Troan <ewt@redhat.com> 
 10   
 11   
 12  # Transport objects 
 13  import os 
 14  import sys 
 15  import time 
 16  from rhn import connections 
 17  from rhn.i18n import sstr, bstr 
 18  from rhn.SmartIO import SmartIO 
 19  from rhn.UserDictCase import UserDictCase 
 20   
 21  try: # python2 
 22      import xmlrpclib 
 23      from types import IntType, StringType, ListType 
 24  except ImportError: # python3 
 25      import xmlrpc.client as xmlrpclib 
 26      IntType = int 
 27      StringType = bytes 
 28      ListType = list 
 29   
 30  __version__ = "$Revision$" 
 31   
 32  # XXX 
 33  COMPRESS_LEVEL = 6 
 34   
 35  # Exceptions 
36 -class NotProcessed(Exception):
37 pass
38
39 -class Transport(xmlrpclib.Transport):
40 user_agent = "rhn.rpclib.py/%s" % __version__ 41
42 - def __init__(self, transfer=0, encoding=0, refreshCallback=None, 43 progressCallback=None, use_datetime=None, timeout=None):
44 self._use_builtin_types = False 45 self._transport_flags = {'transfer' : 0, 'encoding' : 0} 46 self.set_transport_flags(transfer=transfer, encoding=encoding) 47 self._headers = UserDictCase() 48 self.verbose = 0 49 self.connection = None 50 self.method = "POST" 51 self._lang = None 52 self.refreshCallback = refreshCallback 53 self.progressCallback = progressCallback 54 self.bufferSize = 16384 55 self.headers_in = None 56 self.response_status = None 57 self.response_reason = None 58 self._redirected = None 59 self._use_datetime = use_datetime 60 self.timeout = timeout
61 62 # set the progress callback
63 - def set_progress_callback(self, progressCallback, bufferSize=16384):
64 self.progressCallback = progressCallback 65 self.bufferSize = bufferSize
66 67 # set the refresh callback
68 - def set_refresh_callback(self, refreshCallback):
69 self.refreshCallback = refreshCallback
70 71 # set the buffer size 72 # The bigger this is, the faster the read is, but the more seldom is the 73 # progress callback called
74 - def set_buffer_size(self, bufferSize):
75 if bufferSize is None: 76 # No buffer size specified; go with 16k 77 bufferSize = 16384 78 79 self.bufferSize = bufferSize
80 81 # set the request method
82 - def set_method(self, method):
83 if method not in ("GET", "POST"): 84 raise IOError("Unknown request method %s" % method) 85 self.method = method
86 87 # reset the transport options
88 - def set_transport_flags(self, transfer=None, encoding=None, **kwargs):
89 # For backwards compatibility, we keep transfer and encoding as 90 # positional parameters (they could come in as kwargs easily) 91 92 self._transport_flags.update(kwargs) 93 if transfer is not None: 94 self._transport_flags['transfer'] = transfer 95 if encoding is not None: 96 self._transport_flags['encoding'] = encoding 97 self.validate_transport_flags()
98
99 - def get_transport_flags(self):
100 return self._transport_flags.copy()
101
102 - def validate_transport_flags(self):
103 # Transfer and encoding are guaranteed to be there 104 transfer = self._transport_flags.get('transfer') 105 transfer = lookupTransfer(transfer, strict=1) 106 self._transport_flags['transfer'] = transfer 107 108 encoding = self._transport_flags.get('encoding') 109 encoding = lookupEncoding(encoding, strict=1) 110 self._transport_flags['encoding'] = encoding
111 112 # Add arbitrary additional headers.
113 - def set_header(self, name, arg):
114 if type(arg) in [ type([]), type(()) ]: 115 # Multivalued header 116 self._headers[name] = [str(a) for a in arg] 117 else: 118 self._headers[name] = str(arg)
119
120 - def add_header(self, name, arg):
121 if name in self._headers: 122 vlist = self._headers[name] 123 if not isinstance(vlist, ListType): 124 vlist = [ vlist ] 125 else: 126 vlist = self._headers[name] = [] 127 vlist.append(str(arg))
128
129 - def clear_headers(self):
130 self._headers.clear()
131
132 - def get_connection(self, host):
133 if self.verbose: 134 print("Connecting via http to %s" % (host, )) 135 if self.timeout: 136 return connections.HTTPConnection(host, timeout=self.timeout) 137 else: 138 return connections.HTTPConnection(host)
139
140 - def request(self, host, handler, request_body, verbose=0):
141 # issue XML-RPC request 142 # XXX: automatically compute how to send depending on how much data 143 # you want to send 144 145 # XXX Deal with HTTP/1.1 if necessary 146 self.verbose = verbose 147 148 # implement BASIC HTTP AUTHENTICATION 149 host, extra_headers, x509 = self.get_host_info(host) 150 if not extra_headers: 151 extra_headers = [] 152 # Establish the connection 153 connection = self.get_connection(host) 154 # Setting the user agent. Only interesting for SSL tunnels, in any 155 # other case the general headers are good enough. 156 connection.set_user_agent(self.user_agent) 157 if self.verbose: 158 connection.set_debuglevel(self.verbose - 1) 159 # Get the output object to push data with 160 req = Output(connection=connection, method=self.method) 161 req.set_transport_flags(**self._transport_flags) 162 163 # Add the extra headers 164 req.set_header('User-Agent', self.user_agent) 165 for header, value in list(self._headers.items()) + extra_headers: 166 # Output.set_header correctly deals with multivalued headers now 167 req.set_header(header, value) 168 169 # Content-Type 170 req.set_header("Content-Type", "text/xml") 171 req.process(request_body) 172 173 # Host and Content-Length are set by HTTP*Connection 174 for h in ['Content-Length', 'Host']: 175 req.clear_header(h) 176 177 headers, fd = req.send_http(host, handler) 178 179 if self.verbose: 180 print("Incoming headers:") 181 for header, value in headers.items(): 182 print("\t%s : %s" % (header, value)) 183 184 if fd.status in (301, 302): 185 self._redirected = headers["Location"] 186 self.response_status = fd.status 187 return None 188 189 # Save the headers 190 self.headers_in = headers 191 self.response_status = fd.status 192 self.response_reason = fd.reason 193 194 return self._process_response(fd, connection)
195
196 - def _process_response(self, fd, connection):
197 # Now use the Input class in case we get an enhanced response 198 resp = Input(self.headers_in, progressCallback=self.progressCallback, 199 bufferSize=self.bufferSize) 200 201 fd = resp.decode(fd) 202 203 if isinstance(fd, InputStream): 204 # When the File object goes out of scope, so will the InputStream; 205 # that will eventually call the connection's close() method and 206 # cleanly reap it 207 f = File(fd.fd, fd.length, fd.name, bufferSize=self.bufferSize, 208 progressCallback=self.progressCallback) 209 # Set the File's close method to the connection's 210 # Note that calling the HTTPResponse's close() is not enough, 211 # since the main socket would remain open, and this is 212 # particularily bad with SSL 213 f.close = connection.close 214 return f 215 216 # We can safely close the connection now; if we had an 217 # application/octet/stream (for which Input.read passes the original 218 # socket object), Input.decode would return an InputStream, 219 # so we wouldn't reach this point 220 connection.close() 221 222 return self.parse_response(fd)
223 224 # Give back the new URL if redirected
225 - def redirected(self):
226 return self._redirected
227 228 # Rewrite parse_response to provide refresh callbacks
229 - def parse_response(self, f):
230 # read response from input file, and parse it 231 232 p, u = self.getparser() 233 234 while 1: 235 response = f.read(1024) 236 if not response: 237 break 238 if self.refreshCallback: 239 self.refreshCallback() 240 if self.verbose: 241 print("body:", repr(response)) 242 p.feed(response) 243 244 f.close() 245 p.close() 246 return u.close()
247 248
249 - def setlang(self, lang):
250 self._lang = lang
251
252 -class SafeTransport(Transport):
253 - def __init__(self, transfer=0, encoding=0, refreshCallback=None, 254 progressCallback=None, trusted_certs=None, timeout=None):
255 Transport.__init__(self, transfer, encoding, 256 refreshCallback=refreshCallback, progressCallback=progressCallback, 257 timeout=timeout) 258 self.trusted_certs = [] 259 for certfile in (trusted_certs or []): 260 self.add_trusted_cert(certfile)
261
262 - def add_trusted_cert(self, certfile):
263 if not os.access(certfile, os.R_OK): 264 raise ValueError("Certificate file %s is not accessible" % certfile) 265 self.trusted_certs.append(certfile)
266
267 - def get_connection(self, host):
268 # implement BASIC HTTP AUTHENTICATION 269 host, extra_headers, x509 = self.get_host_info(host) 270 if self.verbose: 271 print("Connecting via https to %s" % (host, )) 272 if self.timeout: 273 return connections.HTTPSConnection(host, 274 trusted_certs=self.trusted_certs, timeout=self.timeout) 275 else: 276 return connections.HTTPSConnection(host, 277 trusted_certs=self.trusted_certs)
278 279
280 -class ProxyTransport(Transport):
281 - def __init__(self, proxy, proxyUsername=None, proxyPassword=None, 282 transfer=0, encoding=0, refreshCallback=None, progressCallback=None, 283 timeout=None):
284 Transport.__init__(self, transfer, encoding, 285 refreshCallback=refreshCallback, progressCallback=progressCallback, 286 timeout=timeout) 287 self._proxy = proxy 288 self._proxy_username = proxyUsername 289 self._proxy_password = proxyPassword
290
291 - def get_connection(self, host):
292 if self.verbose: 293 print("Connecting via http to %s proxy %s, username %s, pass %s" % ( 294 host, self._proxy, self._proxy_username, self._proxy_password)) 295 if self.timeout: 296 return connections.HTTPProxyConnection(self._proxy, host, 297 username=self._proxy_username, password=self._proxy_password, 298 timeout=self.timeout) 299 else: 300 return connections.HTTPProxyConnection(self._proxy, host, 301 username=self._proxy_username, password=self._proxy_password)
302
303 -class SafeProxyTransport(ProxyTransport):
304 - def __init__(self, proxy, proxyUsername=None, proxyPassword=None, 305 transfer=0, encoding=0, refreshCallback=None, 306 progressCallback=None, trusted_certs=None, timeout=None):
307 ProxyTransport.__init__(self, proxy, 308 proxyUsername=proxyUsername, proxyPassword=proxyPassword, 309 transfer=transfer, encoding=encoding, 310 refreshCallback=refreshCallback, 311 progressCallback=progressCallback, 312 timeout=timeout) 313 self.trusted_certs = [] 314 for certfile in (trusted_certs or []): 315 self.add_trusted_cert(certfile)
316
317 - def add_trusted_cert(self, certfile):
318 if not os.access(certfile, os.R_OK): 319 raise ValueError("Certificate file %s is not accessible" % certfile) 320 self.trusted_certs.append(certfile)
321
322 - def get_connection(self, host):
323 if self.verbose: 324 print("Connecting via https to %s proxy %s, username %s, pass %s" % ( 325 host, self._proxy, self._proxy_username, self._proxy_password)) 326 if self.timeout: 327 return connections.HTTPSProxyConnection(self._proxy, host, 328 username=self._proxy_username, password=self._proxy_password, 329 trusted_certs=self.trusted_certs, timeout=self.timeout) 330 else: 331 return connections.HTTPSProxyConnection(self._proxy, host, 332 username=self._proxy_username, password=self._proxy_password, 333 trusted_certs=self.trusted_certs)
334 335 # ============================================================================ 336 # Extended capabilities for transport 337 # 338 # We allow for the following possible headers: 339 # 340 # Content-Transfer-Encoding: 341 # This header tells us how the POST data is encoded in what we read. 342 # If it is not set, we assume plain text that can be passed along 343 # without any other modification. If set, valid values are: 344 # - binary : straight binary data 345 # - base64 : will pass through base64 decoder to get the binary data 346 # 347 # Content-Encoding: 348 # This header tells us what should we do with the binary data obtained 349 # after acting on the Content-Transfer-Encoding header. Valid values: 350 # - x-gzip : will need to pass through GNU gunzip-like to get plain 351 # text out 352 # - x-zlib : this denotes the Python's own zlib bindings which are a 353 # datastream based on gzip, but not quite 354 # - x-gpg : will need to pass through GPG to get out the text we want 355 356 # ============================================================================ 357 # Input class to automate reading the posting from the network 358 # Having to work with environment variables blows, though
359 -class Input:
360 - def __init__(self, headers=None, progressCallback=None, bufferSize=1024, 361 max_mem_size=16384):
362 self.transfer = None 363 self.encoding = None 364 self.type = None 365 self.length = 0 366 self.lang = "C" 367 self.name = "" 368 self.progressCallback = progressCallback 369 self.bufferSize = bufferSize 370 self.max_mem_size = max_mem_size 371 372 if not headers: 373 # we need to get them from environment 374 if "HTTP_CONTENT_TRANSFER_ENCODING" in os.environ: 375 self.transfer = os.environ["HTTP_CONTENT_TRANSFER_ENCODING"].lower() 376 if "HTTP_CONTENT_ENCODING" in os.environ: 377 self.encoding = os.environ["HTTP_CONTENT_ENCODING"].lower() 378 if "CONTENT-TYPE" in os.environ: 379 self.type = os.environ["CONTENT-TYPE"].lower() 380 if "CONTENT_LENGTH" in os.environ: 381 self.length = int(os.environ["CONTENT_LENGTH"]) 382 if "HTTP_ACCEPT_LANGUAGE" in os.environ: 383 self.lang = os.environ["HTTP_ACCEPT_LANGUAGE"] 384 if "HTTP_X_PACKAGE_FILENAME" in os.environ: 385 self.name = os.environ["HTTP_X_PACKAGE_FILENAME"] 386 else: 387 # The stupid httplib screws up the headers from the HTTP repsonse 388 # and converts them to lowercase. This means that we have to 389 # convert to lowercase all the dictionary keys in case somebody calls 390 # us with sane values --gaftonc (actually mimetools is the culprit) 391 for header in headers.keys(): 392 value = headers[header] 393 h = header.lower() 394 if h == "content-length": 395 try: 396 self.length = int(value) 397 except ValueError: 398 self.length = 0 399 elif h == "content-transfer-encoding": 400 # RFC 2045 #6.1: case insensitive 401 self.transfer = value.lower() 402 elif h == "content-encoding": 403 # RFC 2616 #3.5: case insensitive 404 self.encoding = value.lower() 405 elif h == "content-type": 406 # RFC 2616 #3.7: case insensitive 407 self.type = value.lower() 408 elif h == "accept-language": 409 # RFC 2616 #3.10: case insensitive 410 self.lang = value.lower() 411 elif h == "x-package-filename": 412 self.name = value 413 414 self.io = None
415
416 - def read(self, fd = sys.stdin):
417 # The octet-streams are passed right back 418 if self.type == "application/octet-stream": 419 return 420 421 if self.length: 422 # Read exactly the amount of data we were told 423 self.io = _smart_read(fd, self.length, 424 bufferSize=self.bufferSize, 425 progressCallback=self.progressCallback, 426 max_mem_size=self.max_mem_size) 427 else: 428 # Oh well, no clue; read until EOF (hopefully) 429 self.io = _smart_total_read(fd) 430 431 if not self.transfer or self.transfer == "binary": 432 return 433 elif self.transfer == "base64": 434 import base64 435 old_io = self.io 436 old_io.seek(0, 0) 437 self.io = SmartIO(max_mem_size=self.max_mem_size) 438 base64.decode(old_io, self.io) 439 else: 440 raise NotImplementedError(self.transfer)
441
442 - def decode(self, fd = sys.stdin):
443 # The octet-stream data are passed right back 444 if self.type == "application/octet-stream": 445 return InputStream(fd, self.length, self.name, close=fd.close) 446 447 if not self.io: 448 self.read(fd) 449 450 # At this point self.io exists (the only case when self.read() does 451 # not initialize self.io is when content-type is 452 # "application/octet-stream" - and we already dealt with that case 453 454 # We can now close the file descriptor 455 if hasattr(fd, "close"): 456 fd.close() 457 458 # Now we have the binary goo 459 if not self.encoding or self.encoding == "__plain": 460 # all is fine. 461 pass 462 elif self.encoding in ("x-zlib", "deflate"): 463 import zlib 464 obj = zlib.decompressobj() 465 self.io.seek(0, 0) 466 data = obj.decompress(self.io.read()) + obj.flush() 467 del obj 468 self.length = len(data) 469 self.io = SmartIO(max_mem_size=self.max_mem_size) 470 self.io.write(data) 471 elif self.encoding in ("x-gzip", "gzip"): 472 import gzip 473 self.io.seek(0, 0) 474 gz = gzip.GzipFile(mode="rb", compresslevel = COMPRESS_LEVEL, 475 fileobj=self.io) 476 data = gz.read() 477 self.length = len(data) 478 self.io = SmartIO(max_mem_size=self.max_mem_size) 479 self.io.write(data) 480 elif self.encoding == "x-gpg": 481 # XXX: should be written 482 raise NotImplementedError(self.transfer, self.encoding) 483 else: 484 raise NotImplementedError(self.transfer, self.encoding) 485 486 # Play nicely and rewind the file descriptor 487 self.io.seek(0, 0) 488 return self.io
489
490 - def getlang(self):
491 return self.lang
492 493 # Utility functions 494
495 -def _smart_total_read(fd, bufferSize=1024, max_mem_size=16384):
496 """ 497 Tries to read data from the supplied stream, and puts the results into a 498 StmartIO object. The data will be in memory or in a temporary file, 499 depending on how much it's been read 500 Returns a SmartIO object 501 """ 502 io = SmartIO(max_mem_size=max_mem_size) 503 while 1: 504 chunk = fd.read(bufferSize) 505 if not chunk: 506 # EOF reached 507 break 508 io.write(chunk) 509 510 return io
511
512 -def _smart_read(fd, amt, bufferSize=1024, progressCallback=None, 513 max_mem_size=16384):
514 # Reads amt bytes from fd, or until the end of file, whichever 515 # occurs first 516 # The function will read in memory if the amout to be read is smaller than 517 # max_mem_size, or to a temporary file otherwise 518 # 519 # Unlike read(), _smart_read tries to return exactly the requested amount 520 # (whereas read will return _up_to_ that amount). Reads from sockets will 521 # usually reaturn less data, or the read can be interrupted 522 # 523 # Inspired by Greg Stein's httplib.py (the standard in python 2.x) 524 # 525 # support for progress callbacks added 526 startTime = time.time() 527 lastTime = startTime 528 buf = SmartIO(max_mem_size=max_mem_size) 529 530 origsize = amt 531 while amt > 0: 532 curTime = time.time() 533 l = min(bufferSize, amt) 534 chunk = fd.read(l) 535 # read guarantees that len(chunk) <= l 536 l = len(chunk) 537 if not l: 538 # Oops. Most likely EOF 539 break 540 541 # And since the original l was smaller than amt, we know amt >= 0 542 amt = amt - l 543 buf.write(chunk) 544 if progressCallback is None: 545 # No progress callback, so don't do fancy computations 546 continue 547 # We update the progress callback if: 548 # we haven't updated it for more than a secord, or 549 # it's the last read (amt == 0) 550 if curTime - lastTime >= 1 or amt == 0: 551 lastTime = curTime 552 # use float() so that we force float division in the next step 553 bytesRead = float(origsize - amt) 554 # if amt == 0, on a fast machine it is possible to have 555 # curTime - lastTime == 0, so add an epsilon to prevent a division 556 # by zero 557 speed = bytesRead / ((curTime - startTime) + .000001) 558 if origsize == 0: 559 secs = 0 560 else: 561 # speed != 0 because bytesRead > 0 562 # (if bytesRead == 0 then origsize == amt, which means a read 563 # of 0 length; but that's impossible since we already checked 564 # that l is non-null 565 secs = amt / speed 566 progressCallback(bytesRead, origsize, speed, secs) 567 568 # Now rewind the SmartIO 569 buf.seek(0, 0) 570 return buf
571
572 -class InputStream:
573 - def __init__(self, fd, length, name = "<unknown>", close=None):
574 self.fd = fd 575 self.length = int(length) 576 self.name = name 577 # Close function 578 self.close = close
579 - def __repr__(self):
580 return "Input data is a stream of %d bytes for file %s.\n" % (self.length, self.name)
581 582 583 # ============================================================================ 584 # Output class that will be used to build the temporary output string
585 -class BaseOutput:
586 # DEFINES for instances use 587 # Content-Encoding 588 ENCODE_NONE = 0 589 ENCODE_GZIP = 1 590 ENCODE_ZLIB = 2 591 ENCODE_GPG = 3 592 593 # Content-Transfer-Encoding 594 TRANSFER_NONE = 0 595 TRANSFER_BINARY = 1 596 TRANSFER_BASE64 = 2 597 598 # Mappings to make things easy 599 encodings = [ 600 [None, "__plain"], # ENCODE_NONE 601 ["x-gzip", "gzip"], # ENCODE_GZIP 602 ["x-zlib", "deflate"], # ENCODE_ZLIB 603 ["x-gpg"], # ENCODE_GPG 604 ] 605 transfers = [ 606 None, # TRANSFER_NONE 607 "binary", # TRANSFRE_BINARY 608 "base64", # TRANSFER_BASE64 609 ] 610
611 - def __init__(self, transfer=0, encoding=0, connection=None, method="POST"):
612 # Assumes connection is an instance of HTTPConnection 613 if connection: 614 if not isinstance(connection, connections.HTTPConnection): 615 raise Exception("Expected an HTTPConnection type object") 616 617 self.method = method 618 619 # Store the connection 620 self._connection = connection 621 622 self.data = None 623 self.headers = UserDictCase() 624 self.encoding = 0 625 self.transfer = 0 626 self.transport_flags = {} 627 # for authenticated proxies 628 self.username = None 629 self.password = None 630 # Fields to keep the information about the server 631 self._host = None 632 self._handler = None 633 self._http_type = None 634 self._protocol = None 635 # Initialize self.transfer and self.encoding 636 self.set_transport_flags(transfer=transfer, encoding=encoding) 637 638 # internal flags 639 self.__processed = 0
640
641 - def set_header(self, name, arg):
642 if type(arg) in [ type([]), type(()) ]: 643 # Multi-valued header 644 # 645 # Per RFC 2616, section 4.2 (Message Headers): 646 # Multiple message-header fields with the same field-name MAY be 647 # present in a message if and only if the entire field-value for 648 # the header field is defined as a comma-separated list [i.e. 649 # #(values)]. It MUST be possible to combine the multiple header 650 # fields into one "field-name: field-value" pair, without 651 # changing the semantics of the message, by appending each 652 # subsequent field-value to the first, each separated by a comma. 653 self.headers[name] = ','.join(map(str, arg)) 654 else: 655 self.headers[name] = str(arg)
656
657 - def clear_header(self, name):
658 if name in self.headers: 659 del self.headers[name]
660
661 - def process(self, data):
662 # Assume straight text/xml 663 self.data = data 664 665 # Content-Encoding header 666 if self.encoding == self.ENCODE_GZIP: 667 import gzip 668 encoding_name = self.encodings[self.ENCODE_GZIP][0] 669 self.set_header("Content-Encoding", encoding_name) 670 f = SmartIO(force_mem=1) 671 gz = gzip.GzipFile(mode="wb", compresslevel=COMPRESS_LEVEL, 672 fileobj = f) 673 if sys.version_info[0] == 3: 674 gz.write(bstr(data)) 675 else: 676 gz.write(sstr(data)) 677 gz.close() 678 self.data = f.getvalue() 679 f.close() 680 elif self.encoding == self.ENCODE_ZLIB: 681 import zlib 682 encoding_name = self.encodings[self.ENCODE_ZLIB][0] 683 self.set_header("Content-Encoding", encoding_name) 684 obj = zlib.compressobj(COMPRESS_LEVEL) 685 self.data = obj.compress(data) + obj.flush() 686 elif self.encoding == self.ENCODE_GPG: 687 # XXX: fix me. 688 raise NotImplementedError(self.transfer, self.encoding) 689 690 # Content-Transfer-Encoding header 691 if self.transfer == self.TRANSFER_BINARY: 692 transfer_name = self.transfers[self.TRANSFER_BINARY] 693 self.set_header("Content-Transfer-Encoding", transfer_name) 694 self.set_header("Content-Type", "application/binary") 695 elif self.transfer == self.TRANSFER_BASE64: 696 import base64 697 transfer_name = self.transfers[self.TRANSFER_BASE64] 698 self.set_header("Content-Transfer-Encoding", transfer_name) 699 self.set_header("Content-Type", "text/base64") 700 self.data = base64.encodestring(self.data) 701 702 self.set_header("Content-Length", len(self.data)) 703 704 rpc_version = __version__ 705 if len(__version__.split()) > 1: 706 rpc_version = __version__.split()[1] 707 708 # other headers 709 self.set_header("X-Transport-Info", 710 'Extended Capabilities Transport (C) Red Hat, Inc (version %s)' % 711 rpc_version) 712 self.__processed = 1
713 714 # reset the transport options
715 - def set_transport_flags(self, transfer=0, encoding=0, **kwargs):
716 self.transfer = transfer 717 self.encoding = encoding 718 self.transport_flags.update(kwargs)
719
720 - def send_http(self, host, handler="/RPC2"):
721 if not self.__processed: 722 raise NotProcessed 723 724 self._host = host 725 726 if self._connection is None: 727 raise Exception("No connection object found") 728 self._connection.connect() 729 # wrap self data into binary object, otherwise HTTPConnection.request 730 # will encode it as ISO-8859-1 https://docs.python.org/3/library/http.client.html#httpconnection-objects 731 self._connection.request(self.method, handler, body=bstr(self.data), headers=self.headers) 732 733 response = self._connection.getresponse() 734 735 if not self.response_acceptable(response): 736 raise xmlrpclib.ProtocolError("%s %s" % 737 (self._host, handler), 738 response.status, response.reason, response.msg) 739 740 # A response object has read() and close() methods, so we can safely 741 # pass the whole object back 742 return response.msg, response
743
744 - def response_acceptable(self, response):
745 """Returns true if the response is acceptable""" 746 if response.status == 200: 747 return 1 748 if response.status in (301, 302): 749 return 1 750 if response.status != 206: 751 return 0 752 # If the flag is not set, it's unacceptable 753 if not self.transport_flags.get('allow_partial_content'): 754 return 0 755 if response.msg['Content-Type'] != 'application/octet-stream': 756 # Don't allow anything else to be requested as a range, it could 757 # break the XML parser 758 return 0 759 return 1
760
761 - def close(self):
762 if self._connection: 763 self._connection.close() 764 self._connection = None
765
766 -def lookupTransfer(transfer, strict=0):
767 """Given a string or numeric representation of a transfer, return the 768 transfer code""" 769 if transfer is None: 770 # Plain 771 return 0 772 if isinstance(transfer, IntType) and 0 <= transfer < len(Output.transfers): 773 return transfer 774 if isinstance(transfer, StringType): 775 for i in range(len(Output.transfers)): 776 if Output.transfers[i] == transfer.lower(): 777 return i 778 if strict: 779 raise ValueError("Unsupported transfer %s" % transfer) 780 # Return default 781 return 0
782
783 -def lookupEncoding(encoding, strict=0):
784 """Given a string or numeric representation of an encoding, return the 785 encoding code""" 786 if encoding is None: 787 # Plain 788 return 0 789 if isinstance(encoding, IntType) and 0 <= encoding < len(Output.encodings): 790 return encoding 791 if isinstance(encoding, StringType): 792 for i in range(len(Output.encodings)): 793 if encoding.lower() in Output.encodings[i]: 794 return i 795 if strict: 796 raise ValueError("Unsupported encoding %s" % encoding) 797 # Return default 798 return 0
799 800 Output = BaseOutput 801 802 # File object
803 -class File:
804 - def __init__(self, file_obj, length = 0, name = None, 805 progressCallback=None, bufferSize=16384):
806 self.length = length 807 self.file_obj = file_obj 808 self.close = file_obj.close 809 self.bufferSize=bufferSize 810 self.name = "" 811 if name: 812 self.name = name[name.rfind("/")+1:] 813 self.progressCallback = progressCallback
814
815 - def __len__(self):
816 return self.length
817
818 - def read(self, amt=None):
819 # If they want to read everything, use _smart_read 820 if amt is None: 821 fd = self._get_file() 822 return fd.read() 823 824 return self.file_obj.read(amt)
825
826 - def read_to_file(self, file):
827 """Copies the contents of this File object into another file 828 object""" 829 fd = self._get_file() 830 while 1: 831 buf = fd.read(self.bufferSize) 832 if not buf: 833 break 834 if sys.version_info[0] == 3: 835 file.write(bstr(buf)) 836 else: 837 file.write(sstr(buf)) 838 return file
839
840 - def _get_file(self):
841 """Read everything into a temporary file and call the progress 842 callbacks if the file length is defined, or just reads till EOF""" 843 if self.length: 844 io = _smart_read(self.file_obj, self.length, 845 bufferSize=self.bufferSize, 846 progressCallback=self.progressCallback) 847 io.seek(0, 0) 848 else: 849 # Read everuthing - no callbacks involved 850 io = _smart_total_read(self.file_obj, bufferSize=self.bufferSize) 851 io.seek(0, 0) 852 return io
853
854 - def __del__(self):
855 if self.close: 856 self.close() 857 self.close = None
858