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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
17
"""http/https transport using pycurl"""
19
from __future__ import absolute_import
21
19
# TODO: test reporting of http errors
23
21
# TODO: Transport option to control caching of particular requests; broadly we
33
31
# from _curl_perform. Not done because we may deprecate pycurl in the
34
32
# future -- vila 20070212
36
35
from cStringIO import StringIO
40
39
from bzrlib import (
43
__version__ as bzrlib_version,
46
from bzrlib.trace import mutter
45
47
from bzrlib.transport.http import (
54
55
except ImportError, e:
55
trace.mutter("failed to import pycurl: %s", e)
56
mutter("failed to import pycurl: %s", e)
56
57
raise errors.DependencyNotPresent('pycurl', e)
59
60
# see if we can actually initialize PyCurl - sometimes it will load but
60
61
# fail to start up due to this bug:
62
63
# 32. (At least on Windows) If libcurl is built with c-ares and there's
63
64
# no DNS server configured in the system, the ares_init() call fails and
64
65
# thus curl_easy_init() fails as well. This causes weird effects for
86
87
return pycurl.__dict__.get(symbol, default)
89
CURLE_SSL_CACERT_BADFILE = _get_pycurl_errcode('E_SSL_CACERT_BADFILE', 77)
88
90
CURLE_COULDNT_CONNECT = _get_pycurl_errcode('E_COULDNT_CONNECT', 7)
89
91
CURLE_COULDNT_RESOLVE_HOST = _get_pycurl_errcode('E_COULDNT_RESOLVE_HOST', 6)
90
92
CURLE_COULDNT_RESOLVE_PROXY = _get_pycurl_errcode('E_COULDNT_RESOLVE_PROXY', 5)
91
93
CURLE_GOT_NOTHING = _get_pycurl_errcode('E_GOT_NOTHING', 52)
92
94
CURLE_PARTIAL_FILE = _get_pycurl_errcode('E_PARTIAL_FILE', 18)
93
95
CURLE_SEND_ERROR = _get_pycurl_errcode('E_SEND_ERROR', 55)
94
CURLE_RECV_ERROR = _get_pycurl_errcode('E_RECV_ERROR', 56)
95
CURLE_SSL_CACERT = _get_pycurl_errcode('E_SSL_CACERT', 60)
96
CURLE_SSL_CACERT_BADFILE = _get_pycurl_errcode('E_SSL_CACERT_BADFILE', 77)
99
98
class PyCurlTransport(HttpTransportBase):
294
286
msg = self._parse_headers(header)
295
287
return code, response.handle_response(abspath, code, msg, data)
298
def _raise_curl_http_error(self, curl, info=None, body=None):
299
"""Common curl->bzrlib error translation.
301
Some methods may choose to override this for particular cases.
303
The URL and code are automatically included as appropriate.
305
:param info: Extra information to include in the message.
307
:param body: File-like object from which the body of the page can be
289
def _raise_curl_http_error(self, curl, info=None):
310
290
code = curl.getinfo(pycurl.HTTP_CODE)
311
291
url = curl.getinfo(pycurl.EFFECTIVE_URL)
313
response_body = body.read()
314
plaintext_body = unhtml_roughly(response_body)
292
# Some error codes can be handled the same way for all
319
295
raise errors.TransportError(
320
296
'Server refuses to fulfill the request (403 Forbidden)'
321
' for %s: %s' % (url, plaintext_body))
326
302
msg = ': ' + info
327
303
raise errors.InvalidHttpResponse(
328
url, 'Unable to handle http code %d%s: %s'
329
% (code, msg, plaintext_body))
331
def _debug_cb(self, kind, text):
332
if kind in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN):
333
self._report_activity(len(text), 'read')
334
if (kind == pycurl.INFOTYPE_HEADER_IN
335
and 'http' in debug.debug_flags):
336
trace.mutter('< %s' % (text.rstrip(),))
337
elif kind in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT):
338
self._report_activity(len(text), 'write')
339
if (kind == pycurl.INFOTYPE_HEADER_OUT
340
and 'http' in debug.debug_flags):
342
for line in text.rstrip().splitlines():
343
# People are often told to paste -Dhttp output to help
344
# debug. Don't compromise credentials.
346
header, details = line.split(':', 1)
349
if header in ('Authorization', 'Proxy-Authorization'):
350
line = '%s: <masked>' % (header,)
352
trace.mutter('> ' + '\n> '.join(lines))
353
elif kind == pycurl.INFOTYPE_TEXT and 'http' in debug.debug_flags:
354
trace.mutter('* %s' % text.rstrip())
355
elif (kind in (pycurl.INFOTYPE_TEXT, pycurl.INFOTYPE_SSL_DATA_IN,
356
pycurl.INFOTYPE_SSL_DATA_OUT)
357
and 'http' in debug.debug_flags):
358
trace.mutter('* %s' % text)
304
url, 'Unable to handle http code %d%s' % (code,msg))
360
306
def _set_curl_options(self, curl):
361
307
"""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)
362
313
ua_str = 'bzr/%s (pycurl: %s)' % (bzrlib.__version__, pycurl.version)
363
314
curl.setopt(pycurl.USERAGENT, ua_str)
364
curl.setopt(pycurl.VERBOSE, 1)
365
curl.setopt(pycurl.DEBUGFUNCTION, self._debug_cb)
366
315
if self.cabundle:
367
316
curl.setopt(pycurl.CAINFO, self.cabundle)
368
317
# Set accepted auth methods
393
342
except pycurl.error, e:
394
343
url = curl.getinfo(pycurl.EFFECTIVE_URL)
395
trace.mutter('got pycurl error: %s, %s, %s, url: %s ',
397
if e[0] in (CURLE_COULDNT_RESOLVE_HOST,
398
CURLE_COULDNT_RESOLVE_PROXY,
344
mutter('got pycurl error: %s, %s, %s, url: %s ',
346
if e[0] in (CURLE_SSL_CACERT_BADFILE,
347
CURLE_COULDNT_RESOLVE_HOST,
399
348
CURLE_COULDNT_CONNECT,
400
349
CURLE_GOT_NOTHING,
402
CURLE_SSL_CACERT_BADFILE,
350
CURLE_COULDNT_RESOLVE_PROXY,):
404
351
raise errors.ConnectionError(
405
352
'curl connection error (%s)\non %s' % (e[1], url))
406
elif e[0] == CURLE_RECV_ERROR:
407
raise errors.ConnectionReset(
408
'curl connection error (%s)\non %s' % (e[1], url))
409
353
elif e[0] == CURLE_PARTIAL_FILE:
410
354
# Pycurl itself has detected a short read. We do not have all
411
355
# the information for the ShortReadvError, but that should be
422
366
redirected_to = msg.getheader('location')
423
367
raise errors.RedirectRequested(url,
425
is_permanent=(code == 301))
369
is_permanent=(code == 301),
370
qual_proto=self._scheme)
428
373
def get_test_permutations():
429
374
"""Return the permutations to be used in testing."""
430
from bzrlib.tests import features
431
from bzrlib.tests import http_server
432
permutations = [(PyCurlTransport, http_server.HttpServer_PyCurl),]
433
if features.HTTPSServerFeature.available():
434
from bzrlib.tests import (
439
class HTTPS_pycurl_transport(PyCurlTransport):
441
def __init__(self, base, _from_transport=None):
442
super(HTTPS_pycurl_transport, self).__init__(base,
444
self.cabundle = str(ssl_certs.build_path('ca.crt'))
446
permutations.append((HTTPS_pycurl_transport,
447
https_server.HTTPSServer_PyCurl))
375
from bzrlib.tests.http_server import HttpServer_PyCurl
376
return [(PyCurlTransport, HttpServer_PyCurl),