35
35
from cStringIO import StringIO
38
39
from bzrlib import (
40
43
__version__ as bzrlib_version,
43
from bzrlib.errors import (NoSuchFile,
46
46
from bzrlib.trace import mutter
47
47
from bzrlib.transport.http import (
56
55
except ImportError, e:
57
56
mutter("failed to import pycurl: %s", e)
58
raise DependencyNotPresent('pycurl', e)
57
raise errors.DependencyNotPresent('pycurl', e)
61
60
# see if we can actually initialize PyCurl - sometimes it will load but
71
70
except pycurl.error, e:
72
71
mutter("failed to initialize pycurl: %s", e)
73
raise DependencyNotPresent('pycurl', e)
72
raise errors.DependencyNotPresent('pycurl', e)
113
112
supported = pycurl.version_info()[8]
114
113
if 'https' not in supported:
115
raise DependencyNotPresent('pycurl', 'no https support')
114
raise errors.DependencyNotPresent('pycurl', 'no https support')
116
115
self.cabundle = ca_bundle.get_ca_path()
118
117
def _get_curl(self):
124
123
# connect to the http server until the first request (which had
125
124
# just called us).
126
125
connection = pycurl.Curl()
127
self._set_connection(connection, None)
126
# First request, initialize credentials.
127
auth = self._create_auth()
128
# Proxy handling is out of reach, so we punt
129
self._set_connection(connection, auth)
128
130
return connection
130
132
def has(self, relpath):
205
raise NoSuchFile(abspath)
207
raise errors.NoSuchFile(abspath)
207
209
self._raise_curl_http_error(
208
210
curl, 'expected 200 or 404 for full response.')
210
212
return code, data
214
# The parent class use 0 to minimize the requests, but since we can't
215
# exploit the results as soon as they are received (pycurl limitation) we'd
216
# better issue more requests and provide a more responsive UI do the cost
217
# of more latency costs.
218
# If you modify this, think about modifying the comment in http/__init__.py
220
_get_max_size = 4 * 1024 * 1024
212
222
def _get_ranged(self, relpath, offsets, tail_amount):
213
223
"""Make a request for just part of the file."""
214
224
curl = self._get_curl()
225
235
code = curl.getinfo(pycurl.HTTP_CODE)
226
# mutter('header:\n%r', header.getvalue())
227
headers = _extract_headers(header.getvalue(), abspath)
228
# handle_response will raise NoSuchFile, etc based on the response code
229
return code, response.handle_response(abspath, code, headers, data)
237
if code == 404: # not found
238
raise errors.NoSuchFile(abspath)
239
elif code in (400, 416):
240
# We don't know which, but one of the ranges we specified was
242
raise errors.InvalidHttpRange(abspath, range_header,
243
'Server return code %d'
244
% curl.getinfo(pycurl.HTTP_CODE))
245
msg = self._parse_headers(header)
246
return code, response.handle_response(abspath, code, msg, data)
248
def _parse_headers(self, status_and_headers):
249
"""Transform the headers provided by curl into an HTTPMessage"""
250
status_and_headers.seek(0)
252
status_and_headers.readline()
253
msg = httplib.HTTPMessage(status_and_headers)
231
256
def _post(self, body_bytes):
232
257
fake_file = StringIO(body_bytes)
243
268
self._curl_perform(curl, header, ['Expect: '])
245
270
code = curl.getinfo(pycurl.HTTP_CODE)
246
headers = _extract_headers(header.getvalue(), abspath)
247
return code, response.handle_response(abspath, code, headers, data)
271
msg = self._parse_headers(header)
272
return code, response.handle_response(abspath, code, msg, data)
249
274
def _raise_curl_http_error(self, curl, info=None):
250
275
code = curl.getinfo(pycurl.HTTP_CODE)
265
290
def _set_curl_options(self, curl):
266
291
"""Set options for all requests"""
267
## curl.setopt(pycurl.VERBOSE, 1)
268
# TODO: maybe include a summary of the pycurl version
269
ua_str = 'bzr/%s (pycurl)' % (bzrlib.__version__,)
292
if 'http' in debug.debug_flags:
293
curl.setopt(pycurl.VERBOSE, 1)
294
# pycurl doesn't implement the CURLOPT_STDERR option, so we can't
295
# do : curl.setopt(pycurl.STDERR, trace._trace_file)
297
ua_str = 'bzr/%s (pycurl: %s)' % (bzrlib.__version__, pycurl.version)
270
298
curl.setopt(pycurl.USERAGENT, ua_str)
271
299
if self.cabundle:
272
300
curl.setopt(pycurl.CAINFO, self.cabundle)
301
# Set accepted auth methods
302
curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_ANY)
303
curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_ANY)
304
auth = self._get_credentials()
305
user = auth.get('user', None)
306
password = auth.get('password', None)
309
userpass = user + ':'
310
if password is not None: # '' is a valid password
312
curl.setopt(pycurl.USERPWD, userpass)
274
314
def _curl_perform(self, curl, header, more_headers=[]):
275
315
"""Perform curl operation and translate exceptions."""
292
332
CURLE_COULDNT_CONNECT,
293
333
CURLE_GOT_NOTHING,
294
334
CURLE_COULDNT_RESOLVE_PROXY,):
295
raise ConnectionError('curl connection error (%s)\non %s'
335
raise errors.ConnectionError(
336
'curl connection error (%s)\non %s' % (e[1], url))
297
337
elif e[0] == CURLE_PARTIAL_FILE:
298
# Pycurl itself has detected a short read. We do
299
# not have all the information for the
300
# ShortReadvError, but that should be enough
338
# Pycurl itself has detected a short read. We do not have all
339
# the information for the ShortReadvError, but that should be
301
341
raise errors.ShortReadvError(url,
302
342
offset='unknown', length='unknown',
303
343
actual='unknown',
304
344
extra='Server aborted the request')
305
# jam 20060713 The code didn't use to re-raise the exception here,
306
# but that seemed bogus
308
346
code = curl.getinfo(pycurl.HTTP_CODE)
309
347
if code in (301, 302, 303, 307):
310
348
url = curl.getinfo(pycurl.EFFECTIVE_URL)
311
headers = _extract_headers(header.getvalue(), url)
312
redirected_to = headers['Location']
349
msg = self._parse_headers(header)
350
redirected_to = msg.getheader('location')
313
351
raise errors.RedirectRequested(url,
315
353
is_permanent=(code == 301),
319
357
def get_test_permutations():
320
358
"""Return the permutations to be used in testing."""
321
from bzrlib.tests.HttpServer import HttpServer_PyCurl
359
from bzrlib.tests.http_server import HttpServer_PyCurl
322
360
return [(PyCurlTransport, HttpServer_PyCurl),