13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
"""http/https transport using pycurl"""
60
60
# see if we can actually initialize PyCurl - sometimes it will load but
61
61
# fail to start up due to this bug:
63
63
# 32. (At least on Windows) If libcurl is built with c-ares and there's
64
64
# no DNS server configured in the system, the ares_init() call fails and
65
65
# thus curl_easy_init() fails as well. This causes weird effects for
87
87
return pycurl.__dict__.get(symbol, default)
89
CURLE_SSL_CACERT_BADFILE = _get_pycurl_errcode('E_SSL_CACERT_BADFILE', 77)
90
89
CURLE_COULDNT_CONNECT = _get_pycurl_errcode('E_COULDNT_CONNECT', 7)
91
90
CURLE_COULDNT_RESOLVE_HOST = _get_pycurl_errcode('E_COULDNT_RESOLVE_HOST', 6)
92
91
CURLE_COULDNT_RESOLVE_PROXY = _get_pycurl_errcode('E_COULDNT_RESOLVE_PROXY', 5)
93
92
CURLE_GOT_NOTHING = _get_pycurl_errcode('E_GOT_NOTHING', 52)
94
93
CURLE_PARTIAL_FILE = _get_pycurl_errcode('E_PARTIAL_FILE', 18)
95
94
CURLE_SEND_ERROR = _get_pycurl_errcode('E_SEND_ERROR', 55)
95
CURLE_RECV_ERROR = _get_pycurl_errcode('E_RECV_ERROR', 56)
96
CURLE_SSL_CACERT = _get_pycurl_errcode('E_SSL_CACERT', 60)
97
CURLE_SSL_CACERT_BADFILE = _get_pycurl_errcode('E_SSL_CACERT_BADFILE', 77)
98
100
class PyCurlTransport(HttpTransportBase):
107
109
def __init__(self, base, _from_transport=None):
108
super(PyCurlTransport, self).__init__(base,
110
super(PyCurlTransport, self).__init__(base, 'pycurl',
109
111
_from_transport=_from_transport)
110
if base.startswith('https'):
112
if self._unqualified_scheme == 'https':
111
113
# Check availability of https into pycurl supported
113
115
supported = pycurl.version_info()[8]
180
182
:param curl: The curl object to place the request on
181
183
:param relpath: The relative path that we want to get
182
:return: (abspath, data, header)
184
:return: (abspath, data, header)
183
185
abspath: full url
184
186
data: file that will be filled with the body
185
187
header: file that will be filled with the headers
215
217
# The parent class use 0 to minimize the requests, but since we can't
216
218
# exploit the results as soon as they are received (pycurl limitation) we'd
217
# better issue more requests and provide a more responsive UI do the cost
218
# of more latency costs.
219
# better issue more requests and provide a more responsive UI incurring
220
# more latency costs.
219
221
# If you modify this, think about modifying the comment in http/__init__.py
221
223
_get_max_size = 4 * 1024 * 1024
286
288
msg = self._parse_headers(header)
287
289
return code, response.handle_response(abspath, code, msg, data)
289
292
def _raise_curl_http_error(self, curl, info=None):
290
293
code = curl.getinfo(pycurl.HTTP_CODE)
291
294
url = curl.getinfo(pycurl.EFFECTIVE_URL)
303
306
raise errors.InvalidHttpResponse(
304
307
url, 'Unable to handle http code %d%s' % (code,msg))
309
def _debug_cb(self, kind, text):
310
if kind in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN,
311
pycurl.INFOTYPE_SSL_DATA_IN):
312
self._report_activity(len(text), 'read')
313
if (kind == pycurl.INFOTYPE_HEADER_IN
314
and 'http' in debug.debug_flags):
315
mutter('< %s' % text)
316
elif kind in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT,
317
pycurl.INFOTYPE_SSL_DATA_OUT):
318
self._report_activity(len(text), 'write')
319
if (kind == pycurl.INFOTYPE_HEADER_OUT
320
and 'http' in debug.debug_flags):
321
mutter('> %s' % text)
322
elif kind == pycurl.INFOTYPE_TEXT and 'http' in debug.debug_flags:
323
mutter('* %s' % text)
306
325
def _set_curl_options(self, curl):
307
326
"""Set options for all requests"""
308
if 'http' in debug.debug_flags:
309
curl.setopt(pycurl.VERBOSE, 1)
310
# pycurl doesn't implement the CURLOPT_STDERR option, so we can't
311
# do : curl.setopt(pycurl.STDERR, trace._trace_file)
313
327
ua_str = 'bzr/%s (pycurl: %s)' % (bzrlib.__version__, pycurl.version)
314
328
curl.setopt(pycurl.USERAGENT, ua_str)
329
curl.setopt(pycurl.VERBOSE, 1)
330
curl.setopt(pycurl.DEBUGFUNCTION, self._debug_cb)
315
331
if self.cabundle:
316
332
curl.setopt(pycurl.CAINFO, self.cabundle)
317
333
# Set accepted auth methods
343
359
url = curl.getinfo(pycurl.EFFECTIVE_URL)
344
360
mutter('got pycurl error: %s, %s, %s, url: %s ',
345
361
e[0], e[1], e, url)
346
if e[0] in (CURLE_SSL_CACERT_BADFILE,
347
CURLE_COULDNT_RESOLVE_HOST,
362
if e[0] in (CURLE_COULDNT_RESOLVE_HOST,
363
CURLE_COULDNT_RESOLVE_PROXY,
348
364
CURLE_COULDNT_CONNECT,
349
365
CURLE_GOT_NOTHING,
350
CURLE_COULDNT_RESOLVE_PROXY,):
367
CURLE_SSL_CACERT_BADFILE,
351
369
raise errors.ConnectionError(
352
370
'curl connection error (%s)\non %s' % (e[1], url))
371
elif e[0] == CURLE_RECV_ERROR:
372
raise errors.ConnectionReset(
373
'curl connection error (%s)\non %s' % (e[1], url))
353
374
elif e[0] == CURLE_PARTIAL_FILE:
354
375
# Pycurl itself has detected a short read. We do not have all
355
376
# the information for the ShortReadvError, but that should be
366
387
redirected_to = msg.getheader('location')
367
388
raise errors.RedirectRequested(url,
369
is_permanent=(code == 301),
370
qual_proto=self._scheme)
390
is_permanent=(code == 301))
373
393
def get_test_permutations():
374
394
"""Return the permutations to be used in testing."""
375
from bzrlib.tests.http_server import HttpServer_PyCurl
376
return [(PyCurlTransport, HttpServer_PyCurl),
395
from bzrlib import tests
396
from bzrlib.tests import http_server
397
permutations = [(PyCurlTransport, http_server.HttpServer_PyCurl),]
398
if tests.HTTPSServerFeature.available():
399
from bzrlib.tests import (
404
class HTTPS_pycurl_transport(PyCurlTransport):
406
def __init__(self, base, _from_transport=None):
407
super(HTTPS_pycurl_transport, self).__init__(base,
409
self.cabundle = str(ssl_certs.build_path('ca.crt'))
411
permutations.append((HTTPS_pycurl_transport,
412
https_server.HTTPSServer_PyCurl))