Package pywbem :: Module cim_http |
[frames] | no frames]

# Source Code for Module pywbem.cim_http

      1  #
      2  # (C) Copyright 2003-2005 Hewlett-Packard Development Company, L.P.
      3  # (C) Copyright 2006-2007 Novell, Inc.
      4  #
      5  # This library is free software; you can redistribute it and/or
      6  # modify it under the terms of the GNU Lesser General Public
      7  # License as published by the Free Software Foundation; either
      8  # version 2.1 of the License, or (at your option) any later version.
      9  #
     10  # This program is distributed in the hope that it will be useful, but
     11  # WITHOUT ANY WARRANTY; without even the implied warranty of
     12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13  # Lesser General Public License for more details.
     14  #
     15  # You should have received a copy of the GNU Lesser General Public
     16  # License along with this program; if not, write to the Free Software
     17  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     18  #
     19  # Author: Tim Potter <tpot@hp.com>
     20  # Author: Martin Pool <mbp@hp.com>
     21  # Author: Bart Whiteley <bwhiteley@suse.de>
     22  #
     23
     24  '''
     25  Send HTTP/HTTPS requests to a WBEM server.
     26
     27  This module does not know anything about the fact that the data being
     28  transferred in the HTTP request and response is CIM-XML.  It is up to the
     29  caller to provide CIM-XML formatted input data and interpret the result data
     30  as CIM-XML.
     31  '''
     32
     33  import string
     34  import re
     35  import os
     36  import sys
     37  import socket
     38  import getpass
     39  from stat import S_ISSOCK
     40  from types import StringTypes
     41  import platform
     42  import httplib
     43  import base64
     44  import urllib
     45  import threading
     46  from datetime import timedelta, datetime
     47
     48  from M2Crypto import SSL, Err
     49
     50  from pywbem import cim_obj
     51
     52  __all__ = ['Error', 'ConnectionError', 'AuthError', 'TimeoutError',
     53             'HTTPTimeout', 'wbem_request', 'get_object_header']
     54
     55 -class Error(Exception):
     56      """Exception base class for catching any HTTP transport related errors."""
     57      pass
     58
     59 -class ConnectionError(Error):
     60      """This exception is raised when there is a problem with the connection
     61      to the server. A retry may or may not succeed."""
     62      pass
     63
     64 -class AuthError(Error):
     65      """This exception is raised when an authentication error (401) occurs."""
     66      pass
     67
     68 -class TimeoutError(Error):
     69      """This exception is raised when the client times out."""
     70      pass
     71
     72 -class HTTPTimeout (object):
     73      """HTTP timeout class that is a context manager (for use by 'with'
     74      statement).
     75
     76      Usage:
     77        ::
     78          with HTTPTimeout(timeout, http_conn):
     79              ... operations using http_conn ...
     80
     81      If the timeout expires, the socket of the HTTP connection is shut down.
     82      Once the http operations return as a result of that or for other reasons,
     83      the exit handler of this class raises a `cim_http.Error` exception in the
     84      thread that executed the ``with`` statement.
     85      """
     86
     87 -    def __init__(self, timeout, http_conn):
     88          """Initialize the HTTPTimeout object.
     89
     90          :Parameters:
     91
     92            timeout : number
     93              Timeout in seconds, ``None`` means no timeout.
     94
     95            http_conn : `httplib.HTTPBaseConnection` (or subclass)
     96              The connection that is to be stopped when the timeout expires.
     97          """
     98
     99          self._timeout = timeout
    100          self._http_conn = http_conn
    101          self._retrytime = 5     # time in seconds after which a retry of the
    102                                  # socket shutdown is scheduled if the socket
    103                                  # is not yet on the connection when the
    104                                  # timeout expires initially.
    105          self._timer = None      # the timer object
    106          self._ts1 = None        # timestamp when timer was started
    107          self._shutdown = None   # flag indicating that the timer handler has
    108                                  # shut down the socket
    109          return
    110
    111 -    def __enter__(self):
    112          if self._timeout != None:
    113              self._timer = threading.Timer(self._timeout,
    114                                            HTTPTimeout.timer_expired, [self])
    115              self._timer.start()
    116              self._ts1 = datetime.now()
    117          self._shutdown = False
    118          return
    119
    120 -    def __exit__(self, exc_type, exc_value, traceback):
    121          if self._timeout != None:
    122              self._timer.cancel()
    123              if self._shutdown:
    124                  # If the timer handler has shut down the socket, we
    125                  # want to make that known, and override any other
    126                  # exceptions that may be pending.
    127                  ts2 = datetime.now()
    128                  duration = ts2 - self._ts1
    129                  duration_sec = float(duration.microseconds)/1000000 +\
    130                                 duration.seconds + duration.days*24*3600
    131                  raise TimeoutError("The client timed out and closed the "\
    132                                     "socket after %.0fs." % duration_sec)
    133          return False # re-raise any other exceptions
    134
    135 -    def timer_expired(self):
    136          """
    137          This method is invoked in context of the timer thread, so we cannot
    138          directly throw exceptions (we can, but they would be in the wrong
    139          thread), so instead we shut down the socket of the connection.
    140          When the timeout happens in early phases of the connection setup,
    141          there is no socket object on the HTTP connection yet, in that case
    142          we retry after the retry duration, indefinitely.
    143          So we do not guarantee in all cases that the overall operation times
    144          out after the specified timeout.
    145          """
    146          if self._http_conn.sock != None:
    147              self._shutdown = True
    148              self._http_conn.sock.shutdown(socket.SHUT_RDWR)
    149          else:
    150              # Retry after the retry duration
    151              self._timer.cancel()
    152              self._timer = threading.Timer(self._retrytime,
    153                                            HTTPTimeout.timer_expired, [self])
    154              self._timer.start()
    155
    156 -def parse_url(url):
    157      """Return a tuple of ``(host, port, ssl)`` from the URL specified in the
    158      ``url`` parameter.
    159
    160      The returned ``ssl`` item is a boolean indicating the use of SSL, and is
    161      recognized from the URL scheme (http vs. https). If none of these schemes
    162      is specified in the URL, the returned value defaults to False
    163      (non-SSL/http).
    164
    165      The returned ``port`` item is the port number, as an integer. If there is
    166      no port number specified in the URL, the returned value defaults to 5988
    167      for non-SSL/http, and to 5989 for SSL/https.
    168
    169      The returned ``host`` item is the host portion of the URL, as a string.
    170      The host portion may be specified in the URL as a short or long host name,
    171      dotted IPv4 address, or bracketed IPv6 address with or without zone index
    172      (aka scope ID). An IPv6 address is converted from the RFC6874 URI syntax
    173      to the RFC4007 text representation syntax before being returned, by
    174      removing the brackets and converting the zone index (if present) from
    175      "-eth0" to "%eth0".
    176
    177      Examples for valid URLs can be found in the test program
    178      `testsuite/test_cim_http.py`.
    179      """
    180
    181      default_port_http  = 5988       # default port for http
    182      default_port_https = 5989       # default port for https
    183      default_ssl        = False      # default SSL use (for no or unknown scheme)
    184
    185      # Look for scheme.
    186      m = re.match(r"^(https?)://(.*)$", url, re.I)
    187      if m:
    188          _scheme = m.group(1).lower()
    189          hostport = m.group(2)
    190          if _scheme == 'https':
    191              ssl = True
    192          else: # will be 'http'
    193              ssl = False
    194      else:
    195          # The URL specified no scheme (or a scheme other than the expected
    196          # schemes, but we don't check)
    197          ssl = default_ssl
    198          hostport = url
    199
    200      # Remove trailing path segments, if any.
    201      # Having URL components other than just slashes (e.g. '#' or '?') is not
    202      # allowed (but we don't check).
    203      m = hostport.find("/")
    204      if m >= 0:
    205          hostport = hostport[0:m]
    206
    207      # Look for port.
    208      # This regexp also works for (colon-separated) IPv6 addresses, because they
    209      # must be bracketed in a URL.
    210      m = re.search(r":([0-9]+)$", hostport)
    211      if m:
    212          host = hostport[0:m.start(0)]
    213          port = int(m.group(1))
    214      else:
    215          host = hostport
    216          port = default_port_https if ssl else default_port_http
    217
    218      # Reformat IPv6 addresses from RFC6874 URI syntax to RFC4007 text
    219      # representation syntax:
    220      #   - Remove the brackets.
    221      #   - Convert the zone index (aka scope ID) from "-eth0" to "%eth0".
    222      # Note on the regexp below: The first group needs the '?' after '.+' to
    223      # become non-greedy; in greedy mode, the optional second group would never
    224      # be matched.
    225      m = re.match(r"^\[(.+?)(?:-(.+))?\]$", host)
    226      if m:
    227          # It is an IPv6 address
    228          host = m.group(1)
    229          if m.group(2) != None:
    230              # The zone index is present
    231              host += "%" + m.group(2)
    232
    233      return host, port, ssl
    234
    235 -def get_default_ca_certs():
    236      """
    237      Try to find out system path with ca certificates. This path is cached and
    238      returned. If no path is found out, None is returned.
    239      """
    240      if not hasattr(get_default_ca_certs, '_path'):
    241          for path in (
    242                  '/etc/pki/ca-trust/extracted/openssl/ca-bundle.trust.crt',
    243                  '/etc/ssl/certs',
    244                  '/etc/ssl/certificates'):
    245              if os.path.exists(path):
    246                  get_default_ca_certs._path = path
    247                  break
    248          else:
    249              get_default_ca_certs._path = None
    250      return get_default_ca_certs._path
    251
    252 -def wbem_request(url, data, creds, headers=[], debug=0, x509=None,
    253                   verify_callback=None, ca_certs=None,
    254                   no_verification=False, timeout=None):
    255      """
    256      Send an HTTP or HTTPS request to a WBEM server and return the response.
    257
    258      This function uses Python's built-in `httplib` module.
    259
    260      :Parameters:
    261
    262        url : `unicode` or UTF-8 encoded `str`
    263          URL of the WBEM server (e.g. ``"https://10.11.12.13:6988"``).
    264          For details, see the ``url`` parameter of
    265          `WBEMConnection.__init__`.
    266
    267        data : `unicode` or UTF-8 encoded `str`
    268          The CIM-XML formatted data to be sent as a request to the WBEM server.
    269
    270        creds
    271          Credentials for authenticating with the WBEM server.
    272          For details, see the ``creds`` parameter of
    273          `WBEMConnection.__init__`.
    274
    275        headers : list of `unicode` or UTF-8 encoded `str`
    276          List of HTTP header fields to be added to the request, in addition to
    277          the standard header fields such as ``Content-type``,
    278          ``Content-length``, and ``Authorization``.
    279
    280        debug : ``bool``
    281          Boolean indicating whether to create debug information.
    282          Not currently used.
    283
    284        x509
    285          Used for HTTPS with certificates.
    286          For details, see the ``x509`` parameter of
    287          `WBEMConnection.__init__`.
    288
    289        verify_callback
    290          Used for HTTPS with certificates.
    291          For details, see the ``verify_callback`` parameter of
    292          `WBEMConnection.__init__`.
    293
    294        ca_certs
    295          Used for HTTPS with certificates.
    296          For details, see the ``ca_certs`` parameter of
    297          `WBEMConnection.__init__`.
    298
    299        no_verification
    300          Used for HTTPS with certificates.
    301          For details, see the ``no_verification`` parameter of
    302          `WBEMConnection.__init__`.
    303
    304        timeout : number
    305          Timeout in seconds, for requests sent to the server. If the server did
    306          not respond within the timeout duration, the socket for the connection
    307          will be closed, causing a `TimeoutError` to be raised.
    308          A value of ``None`` means there is no timeout.
    309          A value of ``0`` means the timeout is very short, and does not really
    310          make any sense.
    311          Note that not all situations can be handled within this timeout, so
    312          for some issues, this method may take longer to raise an exception.
    313
    314      :Returns:
    315        The CIM-XML formatted response data from the WBEM server, as a `unicode`
    316        object.
    317
    318      :Raises:
    319        :raise AuthError:
    320        :raise ConnectionError:
    321        :raise TimeoutError:
    322      """
    323
    324      class HTTPBaseConnection:
    325          def send(self, str):
    326              """ Same as httplib.HTTPConnection.send(), except we don't
    327              check for sigpipe and close the connection.  If the connection
    328              gets closed, getresponse() fails.
    329              """
    330
    331              if self.sock is None:
    332                  if self.auto_open:
    333                      self.connect()
    334                  else:
    335                      raise httplib.NotConnected()
    336              if self.debuglevel > 0:
    337                  print "send:", repr(str)
    338              self.sock.sendall(str)
    339
    340      class HTTPConnection(HTTPBaseConnection, httplib.HTTPConnection):
    341          def __init__(self, host, port=None, strict=None, timeout=None):
    342              httplib.HTTPConnection.__init__(self, host, port, strict, timeout)
    343
    344      class HTTPSConnection(HTTPBaseConnection, httplib.HTTPSConnection):
    345          def __init__(self, host, port=None, key_file=None, cert_file=None,
    346                       strict=None, ca_certs=None, verify_callback=None,
    347                       timeout=None):
    348              httplib.HTTPSConnection.__init__(self, host, port, key_file,
    349                                               cert_file, strict, timeout)
    350              self.ca_certs = ca_certs
    351              self.verify_callback = verify_callback
    352
    353          def connect(self):
    354              "Connect to a host on a given (SSL) port."
    355
    356              # Calling httplib.HTTPSConnection.connect(self) does not work
    357              # because of its ssl.wrap_socket() call. So we copy the code of
    358              # that connect() method modulo the ssl.wrap_socket() call.
    359              #
    360              # Another change is that we do not pass the timeout value
    361              # on to the socket call, because that does not work with M2Crypto.
    362              if sys.version_info[0:2] >= (2, 7):
    363                  # the source_address argument was added in 2.7
    364                  self.sock = socket.create_connection(
    365                              (self.host, self.port), None, self.source_address)
    366              else:
    367                  self.sock = socket.create_connection(
    368                              (self.host, self.port), None)
    369
    370              if self._tunnel_host:
    371                  self._tunnel()
    372              # End of code from httplib.HTTPSConnection.connect(self).
    373
    374              ctx = SSL.Context('sslv23')
    375              if self.cert_file:
    376                  ctx.load_cert(self.cert_file, keyfile=self.key_file)
    377              if self.ca_certs:
    378                  ctx.set_verify(
    379                      SSL.verify_peer | SSL.verify_fail_if_no_peer_cert,
    380                      depth=9, callback=verify_callback)
    381                  if os.path.isdir(self.ca_certs):
    382                      ctx.load_verify_locations(capath=self.ca_certs)
    383                  else:
    384                      ctx.load_verify_locations(cafile=self.ca_certs)
    385              try:
    386                  self.sock = SSL.Connection(ctx, self.sock)
    387                  # Below is a body of SSL.Connection.connect() method
    388                  # except for the first line (socket connection). We want to
    389                  # preserve tunneling ability.
    390
    391                  # Setting the timeout on the input socket does not work
    392                  # with M2Crypto, with such a timeout set it calls a different
    393                  # low level function (nbio instead of bio) that does not work.
    394                  # the symptom is that reading the response returns None.
    395                  # Therefore, we set the timeout at the level of the outer
    396                  # M2Crypto socket object.
    397                  if False: # Currently disabled
    398                      if self.timeout is not None:
    399                          self.sock.set_socket_read_timeout(
    400                                                      SSL.timeout(self.timeout))
    401                          self.sock.set_socket_write_timeout(
    402                                                      SSL.timeout(self.timeout))
    403
    404                  self.sock.addr = (self.host, self.port)
    405                  self.sock.setup_ssl()
    406                  self.sock.set_connect_state()
    407                  ret = self.sock.connect_ssl()
    408                  if self.ca_certs:
    409                      check = getattr(self.sock, 'postConnectionCheck',
    410                                      self.sock.clientPostConnectionCheck)
    411                      if check is not None:
    412                          if not check(self.sock.get_peer_cert(), self.host):
    413                              raise ConnectionError(
    414                                  'SSL error: post connection check failed')
    415                  return ret
    416              except (Err.SSLError, SSL.SSLError, SSL.Checker.WrongHost), arg:
    417                  # This will include SSLTimeoutError (it subclasses SSLError)
    418                  raise ConnectionError(
    419                      "SSL error %s: %s" % (str(arg.__class__), arg))
    420
    421      class FileHTTPConnection(HTTPBaseConnection, httplib.HTTPConnection):
    422
    423          def __init__(self, uds_path):
    424              httplib.HTTPConnection.__init__(self, 'localhost')
    425              self.uds_path = uds_path
    426
    427          def connect(self):
    428              try:
    429                  socket_af = socket.AF_UNIX
    430              except AttributeError:
    431                  raise ConnectionError(
    432                      'file URLs not supported on %s platform due '\
    433                      'to missing AF_UNIX support' % platform.system())
    434              self.sock = socket.socket(socket_af, socket.SOCK_STREAM)
    435              self.sock.connect(self.uds_path)
    436
    437      host, port, use_ssl = parse_url(url)
    438
    439      key_file = None
    440      cert_file = None
    441
    442      if use_ssl and x509 is not None:
    443          cert_file = x509.get('cert_file')
    444          key_file = x509.get('key_file')
    445
    446      numTries = 0
    447      localAuthHeader = None
    448      tryLimit = 5
    449
    450      # Make sure the data argument is converted to a UTF-8 encoded str object.
    451      # This is important because according to RFC2616, the Content-Length HTTP
    452      # header must be measured in Bytes (and the Content-Type header will
    453      # indicate UTF-8).
    454      if isinstance(data, unicode):
    455          data = data.encode('utf-8')
    456
    457      data = '<?xml version="1.0" encoding="utf-8" ?>\n' + data
    458
    459      if not no_verification and ca_certs is None:
    460          ca_certs = get_default_ca_certs()
    461      elif no_verification:
    462          ca_certs = None
    463
    464      local = False
    465      if use_ssl:
    466          h = HTTPSConnection(host,
    467                              port=port,
    468                              key_file=key_file,
    469                              cert_file=cert_file,
    470                              ca_certs=ca_certs,
    471                              verify_callback=verify_callback,
    472                              timeout=timeout)
    473      else:
    474          if url.startswith('http'):
    475              h = HTTPConnection(host,
    476                                 port=port,
    477                                 timeout=timeout)
    478          else:
    479              if url.startswith('file:'):
    480                  url_ = url[5:]
    481              try:
    482                  s = os.stat(url_)
    483                  if S_ISSOCK(s.st_mode):
    484                      h = FileHTTPConnection(url_)
    485                      local = True
    486                  else:
    487                      raise ConnectionError('File URL is not a socket: %s' % url)
    488              except OSError as exc:
    489                  raise ConnectionError('Error with file URL %s: %s' % (url, exc))
    490
    491      locallogin = None
    492      if host in ('localhost', 'localhost6', '127.0.0.1', '::1'):
    493          local = True
    494      if local:
    495          try:
    496              locallogin = getpass.getuser()
    497          except (KeyError, ImportError):
    498              locallogin = None
    499
    500      with HTTPTimeout(timeout, h):
    501
    502          while numTries < tryLimit:
    503              numTries = numTries + 1
    504
    505              h.putrequest('POST', '/cimom')
    506
    507              h.putheader('Content-type', 'application/xml; charset="utf-8"')
    508              h.putheader('Content-length', str(len(data)))
    509              if localAuthHeader is not None:
    510                  h.putheader(*localAuthHeader)
    511              elif creds is not None:
    512                  h.putheader('Authorization', 'Basic %s' %
    513                              base64.encodestring(
    514                                  '%s:%s' %
    515                                  (creds[0], creds[1])).replace('\n', ''))
    516              elif locallogin is not None:
    517                  h.putheader('PegasusAuthorization', 'Local "%s"' % locallogin)
    518
    519              for hdr in headers:
    520                  if isinstance(hdr, unicode):
    521                      hdr = hdr.encode('utf-8')
    522                  s = map(lambda x: string.strip(x), string.split(hdr, ":", 1))
    523                  h.putheader(urllib.quote(s[0]), urllib.quote(s[1]))
    524
    525              try:
    526                  # See RFC 2616 section 8.2.2
    527                  # An http server is allowed to send back an error (presumably
    528                  # a 401), and close the connection without reading the entire
    529                  # request.  A server may do this to protect itself from a DoS
    530                  # attack.
    531                  #
    532                  # If the server closes the connection during our h.send(), we
    533                  # will either get a socket exception 104 (TCP RESET), or a
    534                  # socket exception 32 (broken pipe).  In either case, thanks
    535                  # to our fixed HTTPConnection classes, we'll still be able to
    536                  # retrieve the response so that we can read and respond to the
    537                  # authentication challenge.
    538
    539                  try:
    540                      # endheaders() is the first method in this sequence that
    541                      # actually sends something to the server.
    542                      h.endheaders()
    543                      h.send(data)
    544                  except socket.error as exc:
    545                      # TODO: Verify these errno numbers on Windows vs. Linux
    546                      if exc[0] != 104 and exc[0] != 32:
    547                          raise ConnectionError("Socket error: %s" % exc)
    548
    549                  response = h.getresponse()
    550
    551                  if response.status != 200:
    552                      if response.status == 401:
    553                          if numTries >= tryLimit:
    554                              raise AuthError(response.reason)
    555                          if not local:
    556                              raise AuthError(response.reason)
    557                          authChal = response.getheader('WWW-Authenticate', '')
    558                          if 'openwbem' in response.getheader('Server', ''):
    559                              if 'OWLocal' not in authChal:
    560                                  try:
    561                                      uid = os.getuid()
    562                                  except AttributeError:
    563                                      raise ConnectionError(
    564                                          "OWLocal authorization for OpenWbem "\
    565                                          "server not supported on %s platform "\
    566                                          "due to missing os.getuid()" %\
    567                                          platform.system())
    568                                  localAuthHeader = ('Authorization',
    569                                                     'OWLocal uid="%d"' % uid)
    570                                  continue
    571                              else:
    572                                  try:
    573                                      nonceIdx = authChal.index('nonce=')
    574                                      nonceBegin = authChal.index('"', nonceIdx)
    575                                      nonceEnd = authChal.index('"', nonceBegin+1)
    576                                      nonce = authChal[nonceBegin+1:nonceEnd]
    577                                      cookieIdx = authChal.index('cookiefile=')
    578                                      cookieBegin = authChal.index('"', cookieIdx)
    579                                      cookieEnd = authChal.index('"', cookieBegin+1)
    580                                      cookieFile = authChal[cookieBegin+1:cookieEnd]
    581                                      f = open(cookieFile, 'r')
    582                                      cookie = f.read().strip()
    583                                      f.close()
    584                                      localAuthHeader = (
    585                                          'Authorization',
    586                                          'OWLocal nonce="%s", cookie="%s"' % \
    587                                          (nonce, cookie))
    588                                      continue
    589                                  except:
    590                                      localAuthHeader = None
    591                                      continue
    592                          elif 'Local' in authChal:
    593                              try:
    594                                  beg = authChal.index('"') + 1
    595                                  end = authChal.rindex('"')
    596                                  if end > beg:
    597                                      file = authChal[beg:end]
    598                                      fo = open(file, 'r')
    599                                      cookie = fo.read().strip()
    600                                      fo.close()
    601                                      localAuthHeader = (
    602                                          'PegasusAuthorization',
    603                                          'Local "%s:%s:%s"' % \
    604                                          (locallogin, file, cookie))
    605                                      continue
    606                              except ValueError:
    607                                  pass
    608                          raise AuthError(response.reason)
    609
    610                      cimerror_hdr = response.getheader('CIMError', None)
    611                      if cimerror_hdr is not None:
    612                          exc_str = 'CIMError: %s' % cimerror_hdr
    613                          pgerrordetail_hdr = response.getheader('PGErrorDetail',
    614                                                                 None)
    615                          if pgerrordetail_hdr is not None:
    616                              exc_str += ', PGErrorDetail: %s' %\
    617                                  urllib.unquote(pgerrordetail_hdr)
    618                          raise ConnectionError(exc_str)
    619
    620                      raise ConnectionError('HTTP error: %s' % response.reason)
    621
    622                  body = response.read()
    623
    624              except httplib.BadStatusLine as exc:
    625                  # Background: BadStatusLine is documented to be raised only
    626                  # when strict=True is used (that is not the case here).
    627                  # However, httplib currently raises BadStatusLine also
    628                  # independent of strict when a keep-alive connection times out
    629                  # (e.g. because the server went down).
    630                  # See http://bugs.python.org/issue8450.
    631                  if exc.line is None or exc.line.strip().strip("'") in \
    632                                         ('', 'None'):
    633                      raise ConnectionError("The server closed the "\
    634                          "connection without returning any data, or the "\
    635                          "client timed out")
    636                  else:
    637                      raise ConnectionError("The server returned a bad "\
    638                          "HTTP status line: %r" % exc.line)
    639              except httplib.IncompleteRead as exc:
    640                  raise ConnectionError("HTTP incomplete read: %s" % exc)
    641              except httplib.NotConnected as exc:
    642                  raise ConnectionError("HTTP not connected: %s" % exc)
    643              except socket.error as exc:
    644                  raise ConnectionError("Socket error: %s" % exc)
    645              except socket.sslerror as exc:
    646                  raise ConnectionError("SSL error: %s" % exc)
    647
    648              break
    649
    650      return body
    651
    652
    653 -def get_object_header(obj):
    654      """Return the HTTP header required to make a CIM operation request
    655      using the given object.  Return None if the object does not need
    656      to have a header."""
    657
    658      # Local namespacepath
    659
    660      if isinstance(obj, StringTypes):
    661          return 'CIMObject: %s' % obj
    662
    663      # CIMLocalClassPath
    664
    665      if isinstance(obj, cim_obj.CIMClassName):
    666          return 'CIMObject: %s:%s' % (obj.namespace, obj.classname)
    667
    668      # CIMInstanceName with namespace
    669
    670      if isinstance(obj, cim_obj.CIMInstanceName) and obj.namespace is not None:
    671          return 'CIMObject: %s' % obj
    672
    673      raise TypeError('Don\'t know how to generate HTTP headers for %s' % obj)
    674