~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/_pycurl.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil, Patch Queue Manager, Jelmer Vernooij
  • Date: 2017-01-17 16:20:41 UTC
  • mfrom: (6619.1.2 trunk)
  • Revision ID: tarmac-20170117162041-oo62uk1qsmgc9j31
Merge 2.7 into trunk including fixes for bugs #1622039, #1644003, #1579093 and #1645017. [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2011, 2017 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
"""http/https transport using pycurl"""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
# TODO: test reporting of http errors
20
22
#
21
23
# TODO: Transport option to control caching of particular requests; broadly we
34
36
from cStringIO import StringIO
35
37
import httplib
36
38
 
 
39
import bzrlib
37
40
from bzrlib import (
38
41
    debug,
39
42
    errors,
 
43
    trace,
40
44
    )
41
 
import bzrlib
42
 
from bzrlib.trace import mutter
43
45
from bzrlib.transport.http import (
44
46
    ca_bundle,
45
47
    HttpTransportBase,
46
48
    response,
 
49
    unhtml_roughly,
47
50
    )
48
51
 
49
52
try:
50
53
    import pycurl
51
54
except ImportError, e:
52
 
    mutter("failed to import pycurl: %s", e)
 
55
    trace.mutter("failed to import pycurl: %s", e)
53
56
    raise errors.DependencyNotPresent('pycurl', e)
54
57
 
55
58
try:
64
67
    # reported by Alexander Belchenko, 2006-04-26
65
68
    pycurl.Curl()
66
69
except pycurl.error, e:
67
 
    mutter("failed to initialize pycurl: %s", e)
 
70
    trace.mutter("failed to initialize pycurl: %s", e)
68
71
    raise errors.DependencyNotPresent('pycurl', e)
69
72
 
70
73
 
82
85
    """
83
86
    return pycurl.__dict__.get(symbol, default)
84
87
 
 
88
# Yes, weird but returned on weird http error (invalid status line)
 
89
CURLE_FTP_WEIRD_SERVER_REPLY = _get_pycurl_errcode(
 
90
    'E_FTP_WEIRD_SERVER_REPLY', 8)
85
91
CURLE_COULDNT_CONNECT = _get_pycurl_errcode('E_COULDNT_CONNECT', 7)
86
92
CURLE_COULDNT_RESOLVE_HOST = _get_pycurl_errcode('E_COULDNT_RESOLVE_HOST', 6)
87
93
CURLE_COULDNT_RESOLVE_PROXY = _get_pycurl_errcode('E_COULDNT_RESOLVE_PROXY', 5)
267
273
        # We override the Expect: header so that pycurl will send the POST
268
274
        # body immediately.
269
275
        try:
270
 
            self._curl_perform(curl, header, ['Expect: '])
 
276
            self._curl_perform(curl, header,
 
277
                               ['Expect: ',
 
278
                                'Content-Type: application/octet-stream'])
271
279
        except pycurl.error, e:
272
280
            if e[0] == CURLE_SEND_ERROR:
273
281
                # When talking to an HTTP/1.0 server, getting a 400+ error code
279
287
                # error code and the headers are known to be available, we just
280
288
                # swallow the exception, leaving the upper levels handle the
281
289
                # 400+ error.
282
 
                mutter('got pycurl error in POST: %s, %s, %s, url: %s ',
283
 
                       e[0], e[1], e, abspath)
 
290
                trace.mutter('got pycurl error in POST: %s, %s, %s, url: %s ',
 
291
                             e[0], e[1], e, abspath)
284
292
            else:
285
293
                # Re-raise otherwise
286
294
                raise
290
298
        return code, response.handle_response(abspath, code, msg, data)
291
299
 
292
300
 
293
 
    def _raise_curl_http_error(self, curl, info=None):
 
301
    def _raise_curl_http_error(self, curl, info=None, body=None):
 
302
        """Common curl->bzrlib error translation.
 
303
 
 
304
        Some methods may choose to override this for particular cases.
 
305
 
 
306
        The URL and code are automatically included as appropriate.
 
307
 
 
308
        :param info: Extra information to include in the message.
 
309
 
 
310
        :param body: File-like object from which the body of the page can be
 
311
            read.
 
312
        """
294
313
        code = curl.getinfo(pycurl.HTTP_CODE)
295
314
        url = curl.getinfo(pycurl.EFFECTIVE_URL)
296
 
        # Some error codes can be handled the same way for all
297
 
        # requests
 
315
        if body is not None:
 
316
            response_body = body.read()
 
317
            plaintext_body = unhtml_roughly(response_body)
 
318
        else:
 
319
            response_body = None
 
320
            plaintext_body = ''
298
321
        if code == 403:
299
322
            raise errors.TransportError(
300
323
                'Server refuses to fulfill the request (403 Forbidden)'
301
 
                ' for %s' % url)
 
324
                ' for %s: %s' % (url, plaintext_body))
302
325
        else:
303
326
            if info is None:
304
327
                msg = ''
305
328
            else:
306
329
                msg = ': ' + info
307
330
            raise errors.InvalidHttpResponse(
308
 
                url, 'Unable to handle http code %d%s' % (code,msg))
 
331
                url, 'Unable to handle http code %d%s: %s'
 
332
                % (code, msg, plaintext_body))
309
333
 
310
334
    def _debug_cb(self, kind, text):
311
 
        if kind in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN,
312
 
                    pycurl.INFOTYPE_SSL_DATA_IN):
 
335
        if kind in (pycurl.INFOTYPE_HEADER_IN, pycurl.INFOTYPE_DATA_IN):
313
336
            self._report_activity(len(text), 'read')
314
337
            if (kind == pycurl.INFOTYPE_HEADER_IN
315
338
                and 'http' in debug.debug_flags):
316
 
                mutter('< %s' % text)
317
 
        elif kind in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT,
318
 
                      pycurl.INFOTYPE_SSL_DATA_OUT):
 
339
                trace.mutter('< %s' % (text.rstrip(),))
 
340
        elif kind in (pycurl.INFOTYPE_HEADER_OUT, pycurl.INFOTYPE_DATA_OUT):
319
341
            self._report_activity(len(text), 'write')
320
342
            if (kind == pycurl.INFOTYPE_HEADER_OUT
321
343
                and 'http' in debug.debug_flags):
322
 
                mutter('> %s' % text)
 
344
                lines = []
 
345
                for line in text.rstrip().splitlines():
 
346
                    # People are often told to paste -Dhttp output to help
 
347
                    # debug. Don't compromise credentials.
 
348
                    try:
 
349
                        header, details = line.split(':', 1)
 
350
                    except ValueError:
 
351
                        header = None
 
352
                    if header in ('Authorization', 'Proxy-Authorization'):
 
353
                        line = '%s: <masked>' % (header,)
 
354
                    lines.append(line)
 
355
                trace.mutter('> ' + '\n> '.join(lines))
323
356
        elif kind == pycurl.INFOTYPE_TEXT and 'http' in debug.debug_flags:
324
 
            mutter('* %s' % text)
 
357
            trace.mutter('* %s' % text.rstrip())
 
358
        elif (kind in (pycurl.INFOTYPE_TEXT, pycurl.INFOTYPE_SSL_DATA_IN,
 
359
                       pycurl.INFOTYPE_SSL_DATA_OUT)
 
360
              and 'http' in debug.debug_flags):
 
361
            trace.mutter('* %s' % text)
325
362
 
326
363
    def _set_curl_options(self, curl):
327
364
        """Set options for all requests"""
358
395
            curl.perform()
359
396
        except pycurl.error, e:
360
397
            url = curl.getinfo(pycurl.EFFECTIVE_URL)
361
 
            mutter('got pycurl error: %s, %s, %s, url: %s ',
362
 
                    e[0], e[1], e, url)
 
398
            trace.mutter('got pycurl error: %s, %s, %s, url: %s ',
 
399
                         e[0], e[1], e, url)
363
400
            if e[0] in (CURLE_COULDNT_RESOLVE_HOST,
364
401
                        CURLE_COULDNT_RESOLVE_PROXY,
365
402
                        CURLE_COULDNT_CONNECT,
 
403
                        CURLE_FTP_WEIRD_SERVER_REPLY,
366
404
                        CURLE_GOT_NOTHING,
367
405
                        CURLE_SSL_CACERT,
368
406
                        CURLE_SSL_CACERT_BADFILE,
393
431
 
394
432
def get_test_permutations():
395
433
    """Return the permutations to be used in testing."""
396
 
    from bzrlib import tests
 
434
    from bzrlib.tests import features
397
435
    from bzrlib.tests import http_server
398
436
    permutations = [(PyCurlTransport, http_server.HttpServer_PyCurl),]
399
 
    if tests.HTTPSServerFeature.available():
 
437
    if features.HTTPSServerFeature.available():
400
438
        from bzrlib.tests import (
401
439
            https_server,
402
440
            ssl_certs,