~ubuntu-branches/ubuntu/saucy/python-glanceclient/saucy

« back to all changes in this revision

Viewing changes to glanceclient/common/http.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short, Adam Gandelman, Chuck Short
  • Date: 2012-11-23 10:22:06 UTC
  • mfrom: (1.1.6)
  • Revision ID: package-import@ubuntu.com-20121123102206-0dlq52aydhudnrrc
Tags: 1:0.6.0-0ubuntu1
[ Adam Gandelman ]
* Ensure python-prettytable >= 0.6. (LP: #1073275)
* debian/control, pydist-overrides: Add python-openssl override.

[ Chuck Short ]
* New usptream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
import copy
17
17
import httplib
18
18
import logging
19
 
import os
 
19
import posixpath
20
20
import socket
21
21
import StringIO
 
22
import struct
22
23
import urlparse
23
24
 
24
25
try:
25
 
    import ssl
26
 
except ImportError:
27
 
    #TODO(bcwaldon): Handle this failure more gracefully
28
 
    pass
29
 
 
30
 
try:
31
26
    import json
32
27
except ImportError:
33
28
    import simplejson as json
37
32
    import cgi
38
33
    urlparse.parse_qsl = cgi.parse_qsl
39
34
 
 
35
import OpenSSL
40
36
 
41
37
from glanceclient import exc
42
38
 
50
46
 
51
47
    def __init__(self, endpoint, **kwargs):
52
48
        self.endpoint = endpoint
 
49
        endpoint_parts = self.parse_endpoint(self.endpoint)
 
50
        self.endpoint_scheme = endpoint_parts.scheme
 
51
        self.endpoint_hostname = endpoint_parts.hostname
 
52
        self.endpoint_port = endpoint_parts.port
 
53
        self.endpoint_path = endpoint_parts.path
 
54
 
 
55
        self.connection_class = self.get_connection_class(self.endpoint_scheme)
 
56
        self.connection_kwargs = self.get_connection_kwargs(
 
57
                self.endpoint_scheme, **kwargs)
 
58
 
53
59
        self.auth_token = kwargs.get('token')
54
 
        self.connection_params = self.get_connection_params(endpoint, **kwargs)
55
 
 
56
 
    @staticmethod
57
 
    def get_connection_params(endpoint, **kwargs):
58
 
        parts = urlparse.urlparse(endpoint)
59
 
 
60
 
        _args = (parts.hostname, parts.port)
 
60
 
 
61
    @staticmethod
 
62
    def parse_endpoint(endpoint):
 
63
        return urlparse.urlparse(endpoint)
 
64
 
 
65
    @staticmethod
 
66
    def get_connection_class(scheme):
 
67
        if scheme == 'https':
 
68
            return VerifiedHTTPSConnection
 
69
        else:
 
70
            return httplib.HTTPConnection
 
71
 
 
72
    @staticmethod
 
73
    def get_connection_kwargs(scheme, **kwargs):
61
74
        _kwargs = {'timeout': float(kwargs.get('timeout', 600))}
62
75
 
63
 
        if parts.scheme == 'https':
64
 
            _class = VerifiedHTTPSConnection
 
76
        if scheme == 'https':
65
77
            _kwargs['ca_file'] = kwargs.get('ca_file', None)
66
78
            _kwargs['cert_file'] = kwargs.get('cert_file', None)
67
79
            _kwargs['key_file'] = kwargs.get('key_file', None)
68
80
            _kwargs['insecure'] = kwargs.get('insecure', False)
69
 
        elif parts.scheme == 'http':
70
 
            _class = httplib.HTTPConnection
71
 
        else:
72
 
            msg = 'Unsupported scheme: %s' % parts.scheme
73
 
            raise exc.InvalidEndpoint(msg)
 
81
            _kwargs['ssl_compression'] = kwargs.get('ssl_compression', True)
74
82
 
75
 
        return (_class, _args, _kwargs)
 
83
        return _kwargs
76
84
 
77
85
    def get_connection(self):
78
 
        _class = self.connection_params[0]
 
86
        _class = self.connection_class
79
87
        try:
80
 
            return _class(*self.connection_params[1],
81
 
                          **self.connection_params[2])
 
88
            return _class(self.endpoint_hostname, self.endpoint_port,
 
89
                          **self.connection_kwargs)
82
90
        except httplib.InvalidURL:
83
91
            raise exc.InvalidEndpoint()
84
92
 
95
103
            ('ca_file', '--cacert %s'),
96
104
        ]
97
105
        for (key, fmt) in conn_params_fmt:
98
 
            value = self.connection_params[2].get(key)
 
106
            value = self.connection_kwargs.get(key)
99
107
            if value:
100
108
                curl.append(fmt % value)
101
109
 
102
 
        if self.connection_params[2].get('insecure'):
 
110
        if self.connection_kwargs.get('insecure'):
103
111
            curl.append('-k')
104
112
 
105
113
        if 'body' in kwargs:
134
142
        conn = self.get_connection()
135
143
 
136
144
        try:
137
 
            conn.request(method, url, **kwargs)
 
145
            conn_url = posixpath.normpath('%s/%s' % (self.endpoint_path, url))
 
146
            if kwargs['headers'].get('Transfer-Encoding') == 'chunked':
 
147
                conn.putrequest(method, conn_url)
 
148
                for header, value in kwargs['headers'].items():
 
149
                    conn.putheader(header, value)
 
150
                conn.endheaders()
 
151
                chunk = kwargs['body'].read(CHUNKSIZE)
 
152
                # Chunk it, baby...
 
153
                while chunk:
 
154
                    conn.send('%x\r\n%s\r\n' % (len(chunk), chunk))
 
155
                    chunk = kwargs['body'].read(CHUNKSIZE)
 
156
                conn.send('0\r\n\r\n')
 
157
            else:
 
158
                conn.request(method, conn_url, **kwargs)
138
159
            resp = conn.getresponse()
139
160
        except socket.gaierror as e:
140
161
            message = "Error finding address for %(url)s: %(e)s" % locals()
141
162
            raise exc.InvalidEndpoint(message=message)
142
163
        except (socket.error, socket.timeout) as e:
143
 
            message = "Error communicating with %(url)s: %(e)s" % locals()
 
164
            endpoint = self.endpoint
 
165
            message = "Error communicating with %(endpoint)s %(e)s" % locals()
144
166
            raise exc.CommunicationError(message=message)
145
167
 
146
168
        body_iter = ResponseBodyIterator(resp)
154
176
            self.log_http_response(resp)
155
177
 
156
178
        if 400 <= resp.status < 600:
157
 
            LOG.exception("Request returned failure status.")
 
179
            LOG.error("Request returned failure status.")
158
180
            raise exc.from_response(resp)
159
181
        elif resp.status in (301, 302, 305):
160
182
            # Redirected. Reissue the request to the new location.
188
210
        kwargs.setdefault('headers', {})
189
211
        kwargs['headers'].setdefault('Content-Type',
190
212
                                     'application/octet-stream')
 
213
        if 'body' in kwargs:
 
214
            if (hasattr(kwargs['body'], 'read')
 
215
                and method.lower() in ('post', 'put')):
 
216
                # We use 'Transfer-Encoding: chunked' because
 
217
                # body size may not always be known in advance.
 
218
                kwargs['headers']['Transfer-Encoding'] = 'chunked'
191
219
        return self._http_request(url, method, **kwargs)
192
220
 
193
221
 
 
222
class OpenSSLConnectionDelegator(object):
 
223
    """
 
224
    An OpenSSL.SSL.Connection delegator.
 
225
 
 
226
    Supplies an additional 'makefile' method which httplib requires
 
227
    and is not present in OpenSSL.SSL.Connection.
 
228
 
 
229
    Note: Since it is not possible to inherit from OpenSSL.SSL.Connection
 
230
    a delegator must be used.
 
231
    """
 
232
    def __init__(self, *args, **kwargs):
 
233
        self.connection = OpenSSL.SSL.Connection(*args, **kwargs)
 
234
 
 
235
    def __getattr__(self, name):
 
236
        return getattr(self.connection, name)
 
237
 
 
238
    def makefile(self, *args, **kwargs):
 
239
        return socket._fileobject(self.connection, *args, **kwargs)
 
240
 
 
241
 
194
242
class VerifiedHTTPSConnection(httplib.HTTPSConnection):
195
 
    """httplib-compatibile connection using client-side SSL authentication
196
 
 
197
 
    :see http://code.activestate.com/recipes/
198
 
            577548-https-httplib-client-connection-with-certificate-v/
199
 
    """
200
 
 
 
243
    """
 
244
    Extended HTTPSConnection which uses the OpenSSL library
 
245
    for enhanced SSL support.
 
246
    """
201
247
    def __init__(self, host, port, key_file=None, cert_file=None,
202
 
                 ca_file=None, timeout=None, insecure=False):
203
 
        httplib.HTTPSConnection.__init__(self, host, port, key_file=key_file,
 
248
                 ca_file=None, timeout=None, insecure=False,
 
249
                 ssl_compression=True):
 
250
        httplib.HTTPSConnection.__init__(self, host, port,
 
251
                                         key_file=key_file,
204
252
                                         cert_file=cert_file)
205
253
        self.key_file = key_file
206
254
        self.cert_file = cert_file
207
 
        if ca_file is not None:
208
 
            self.ca_file = ca_file
209
 
        else:
210
 
            self.ca_file = self.get_system_ca_file()
211
255
        self.timeout = timeout
212
256
        self.insecure = insecure
 
257
        self.ssl_compression = ssl_compression
 
258
        self.ca_file = ca_file
 
259
        self.setcontext()
 
260
 
 
261
    @staticmethod
 
262
    def verify_callback(connection, x509, errnum, errdepth, preverify_ok):
 
263
        # Pass through OpenSSL's default result
 
264
        return preverify_ok
 
265
 
 
266
    def setcontext(self):
 
267
        """
 
268
        Set up the OpenSSL context.
 
269
        """
 
270
        self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
 
271
 
 
272
        if self.ssl_compression is False:
 
273
            self.context.set_options(0x20000)  # SSL_OP_NO_COMPRESSION
 
274
 
 
275
        if self.insecure is not True:
 
276
            self.context.set_verify(OpenSSL.SSL.VERIFY_PEER,
 
277
                                    self.verify_callback)
 
278
        else:
 
279
            self.context.set_verify(OpenSSL.SSL.VERIFY_NONE,
 
280
                                    self.verify_callback)
 
281
 
 
282
        if self.cert_file:
 
283
            try:
 
284
                self.context.use_certificate_file(self.cert_file)
 
285
            except Exception, e:
 
286
                msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e)
 
287
                raise exc.SSLConfigurationError(msg)
 
288
            if self.key_file is None:
 
289
                # We support having key and cert in same file
 
290
                try:
 
291
                    self.context.use_privatekey_file(self.cert_file)
 
292
                except Exception, e:
 
293
                    msg = ('No key file specified and unable to load key '
 
294
                           'from "%s" %s' % (self.cert_file, e))
 
295
                    raise exc.SSLConfigurationError(msg)
 
296
 
 
297
        if self.key_file:
 
298
            try:
 
299
                self.context.use_privatekey_file(self.key_file)
 
300
            except Exception, e:
 
301
                msg = 'Unable to load key from "%s" %s' % (self.key_file, e)
 
302
                raise exc.SSLConfigurationError(msg)
 
303
 
 
304
        if self.ca_file:
 
305
            try:
 
306
                self.context.load_verify_locations(self.ca_file)
 
307
            except Exception, e:
 
308
                msg = 'Unable to load CA from "%s"' % (self.ca_file, e)
 
309
                raise exc.SSLConfigurationError(msg)
 
310
        else:
 
311
            self.context.set_default_verify_paths()
213
312
 
214
313
    def connect(self):
215
314
        """
216
 
        Connect to a host on a given (SSL) port.
217
 
        If ca_file is pointing somewhere, use it to check Server Certificate.
218
 
 
219
 
        Redefined/copied and extended from httplib.py:1105 (Python 2.6.x).
220
 
        This is needed to pass cert_reqs=ssl.CERT_REQUIRED as parameter to
221
 
        ssl.wrap_socket(), which forces SSL to check server certificate against
222
 
        our client certificate.
 
315
        Connect to an SSL port using the OpenSSL library and apply
 
316
        per-connection parameters.
223
317
        """
224
 
        sock = socket.create_connection((self.host, self.port), self.timeout)
225
 
 
226
 
        if self._tunnel_host:
227
 
            self.sock = sock
228
 
            self._tunnel()
229
 
 
230
 
        if self.insecure is True:
231
 
            kwargs = {'cert_reqs': ssl.CERT_NONE}
232
 
        else:
233
 
            kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
234
 
 
235
 
        if self.cert_file:
236
 
            kwargs['certfile'] = self.cert_file
237
 
            if self.key_file:
238
 
                kwargs['keyfile'] = self.key_file
239
 
 
240
 
        self.sock = ssl.wrap_socket(sock, **kwargs)
241
 
 
242
 
    @staticmethod
243
 
    def get_system_ca_file():
244
 
        """"Return path to system default CA file"""
245
 
        # Standard CA file locations for Debian/Ubuntu, RedHat/Fedora,
246
 
        # Suse, FreeBSD/OpenBSD
247
 
        ca_path = ['/etc/ssl/certs/ca-certificates.crt',
248
 
                   '/etc/pki/tls/certs/ca-bundle.crt',
249
 
                   '/etc/ssl/ca-bundle.pem',
250
 
                   '/etc/ssl/cert.pem']
251
 
        for ca in ca_path:
252
 
            if os.path.exists(ca):
253
 
                return ca
254
 
        return None
 
318
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
319
        if self.timeout is not None:
 
320
            # '0' microseconds
 
321
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO,
 
322
                            struct.pack('LL', self.timeout, 0))
 
323
        self.sock = OpenSSLConnectionDelegator(self.context, sock)
 
324
        self.sock.connect((self.host, self.port))
255
325
 
256
326
 
257
327
class ResponseBodyIterator(object):