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
55
self.connection_class = self.get_connection_class(self.endpoint_scheme)
56
self.connection_kwargs = self.get_connection_kwargs(
57
self.endpoint_scheme, **kwargs)
53
59
self.auth_token = kwargs.get('token')
54
self.connection_params = self.get_connection_params(endpoint, **kwargs)
57
def get_connection_params(endpoint, **kwargs):
58
parts = urlparse.urlparse(endpoint)
60
_args = (parts.hostname, parts.port)
62
def parse_endpoint(endpoint):
63
return urlparse.urlparse(endpoint)
66
def get_connection_class(scheme):
68
return VerifiedHTTPSConnection
70
return httplib.HTTPConnection
73
def get_connection_kwargs(scheme, **kwargs):
61
74
_kwargs = {'timeout': float(kwargs.get('timeout', 600))}
63
if parts.scheme == 'https':
64
_class = VerifiedHTTPSConnection
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
72
msg = 'Unsupported scheme: %s' % parts.scheme
73
raise exc.InvalidEndpoint(msg)
81
_kwargs['ssl_compression'] = kwargs.get('ssl_compression', True)
75
return (_class, _args, _kwargs)
77
85
def get_connection(self):
78
_class = self.connection_params[0]
86
_class = self.connection_class
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()
134
142
conn = self.get_connection()
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)
151
chunk = kwargs['body'].read(CHUNKSIZE)
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')
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)
146
168
body_iter = ResponseBodyIterator(resp)
188
210
kwargs.setdefault('headers', {})
189
211
kwargs['headers'].setdefault('Content-Type',
190
212
'application/octet-stream')
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)
222
class OpenSSLConnectionDelegator(object):
224
An OpenSSL.SSL.Connection delegator.
226
Supplies an additional 'makefile' method which httplib requires
227
and is not present in OpenSSL.SSL.Connection.
229
Note: Since it is not possible to inherit from OpenSSL.SSL.Connection
230
a delegator must be used.
232
def __init__(self, *args, **kwargs):
233
self.connection = OpenSSL.SSL.Connection(*args, **kwargs)
235
def __getattr__(self, name):
236
return getattr(self.connection, name)
238
def makefile(self, *args, **kwargs):
239
return socket._fileobject(self.connection, *args, **kwargs)
194
242
class VerifiedHTTPSConnection(httplib.HTTPSConnection):
195
"""httplib-compatibile connection using client-side SSL authentication
197
:see http://code.activestate.com/recipes/
198
577548-https-httplib-client-connection-with-certificate-v/
244
Extended HTTPSConnection which uses the OpenSSL library
245
for enhanced SSL support.
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,
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
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
262
def verify_callback(connection, x509, errnum, errdepth, preverify_ok):
263
# Pass through OpenSSL's default result
266
def setcontext(self):
268
Set up the OpenSSL context.
270
self.context = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
272
if self.ssl_compression is False:
273
self.context.set_options(0x20000) # SSL_OP_NO_COMPRESSION
275
if self.insecure is not True:
276
self.context.set_verify(OpenSSL.SSL.VERIFY_PEER,
277
self.verify_callback)
279
self.context.set_verify(OpenSSL.SSL.VERIFY_NONE,
280
self.verify_callback)
284
self.context.use_certificate_file(self.cert_file)
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
291
self.context.use_privatekey_file(self.cert_file)
293
msg = ('No key file specified and unable to load key '
294
'from "%s" %s' % (self.cert_file, e))
295
raise exc.SSLConfigurationError(msg)
299
self.context.use_privatekey_file(self.key_file)
301
msg = 'Unable to load key from "%s" %s' % (self.key_file, e)
302
raise exc.SSLConfigurationError(msg)
306
self.context.load_verify_locations(self.ca_file)
308
msg = 'Unable to load CA from "%s"' % (self.ca_file, e)
309
raise exc.SSLConfigurationError(msg)
311
self.context.set_default_verify_paths()
214
313
def connect(self):
216
Connect to a host on a given (SSL) port.
217
If ca_file is pointing somewhere, use it to check Server Certificate.
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.
224
sock = socket.create_connection((self.host, self.port), self.timeout)
226
if self._tunnel_host:
230
if self.insecure is True:
231
kwargs = {'cert_reqs': ssl.CERT_NONE}
233
kwargs = {'cert_reqs': ssl.CERT_REQUIRED, 'ca_certs': self.ca_file}
236
kwargs['certfile'] = self.cert_file
238
kwargs['keyfile'] = self.key_file
240
self.sock = ssl.wrap_socket(sock, **kwargs)
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',
252
if os.path.exists(ca):
318
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
319
if self.timeout is not None:
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))
257
327
class ResponseBodyIterator(object):