~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Patch Queue Manager
  • Date: 2016-01-15 09:21:49 UTC
  • mfrom: (6606.2.1 autodoc-unicode)
  • Revision ID: pqm@pqm.ubuntu.com-20160115092149-z5f4sfq3jvaz0enb
(vila) Fix autodoc runner when LANG=C. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2012 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
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
 
"""Implementaion of urllib2 tailored to bzr needs
 
17
"""Implementation of urllib2 tailored to bzr needs
18
18
 
19
19
This file complements the urllib2 class hierarchy with custom classes.
20
20
 
36
36
request (see AbstractHTTPHandler.do_open).
37
37
"""
38
38
 
 
39
from __future__ import absolute_import
 
40
 
39
41
DEBUG = 0
40
42
 
41
43
# FIXME: Oversimplifying, two kind of exceptions should be
48
50
 
49
51
import errno
50
52
import httplib
51
 
try:
52
 
    import kerberos
53
 
except ImportError:
54
 
    have_kerberos = False
55
 
else:
56
 
    have_kerberos = True
 
53
import os
57
54
import socket
58
55
import urllib
59
56
import urllib2
67
64
    config,
68
65
    debug,
69
66
    errors,
 
67
    lazy_import,
70
68
    osutils,
71
69
    trace,
72
70
    transport,
73
71
    ui,
74
72
    urlutils,
75
73
    )
 
74
lazy_import.lazy_import(globals(), """
 
75
import ssl
 
76
""")
 
77
 
 
78
 
 
79
# Note for packagers: if there is no package providing certs for your platform,
 
80
# the curl project produces http://curl.haxx.se/ca/cacert.pem weekly.
 
81
_ssl_ca_certs_known_locations = [
 
82
    u'/etc/ssl/certs/ca-certificates.crt', # Ubuntu/debian/gentoo
 
83
    u'/etc/pki/tls/certs/ca-bundle.crt', # Fedora/CentOS/RH
 
84
    u'/etc/ssl/ca-bundle.pem', # OpenSuse
 
85
    u'/etc/ssl/cert.pem', # OpenSuse
 
86
    u"/usr/local/share/certs/ca-root-nss.crt", # FreeBSD
 
87
    # XXX: Needs checking, can't trust the interweb ;) -- vila 2012-01-25
 
88
    u'/etc/openssl/certs/ca-certificates.crt', # Solaris
 
89
    ]
 
90
def default_ca_certs():
 
91
    if sys.platform == 'win32':
 
92
        return os.path.join(os.path.dirname(sys.executable), u"cacert.pem")
 
93
    elif sys.platform == 'darwin':
 
94
        # FIXME: Needs some default value for osx, waiting for osx installers
 
95
        # guys feedback -- vila 2012-01-25
 
96
        pass
 
97
    else:
 
98
        # Try known locations for friendly OSes providing the root certificates
 
99
        # without making them hard to use for any https client.
 
100
        for path in _ssl_ca_certs_known_locations:
 
101
            if os.path.exists(path):
 
102
                # First found wins
 
103
                return path
 
104
    # A default path that makes sense and will be mentioned in the error
 
105
    # presented to the user, even if not correct for all platforms
 
106
    return _ssl_ca_certs_known_locations[0]
 
107
 
 
108
 
 
109
def ca_certs_from_store(path):
 
110
    if not os.path.exists(path):
 
111
        raise ValueError("ca certs path %s does not exist" % path)
 
112
    return path
 
113
 
 
114
 
 
115
def cert_reqs_from_store(unicode_str):
 
116
    import ssl
 
117
    try:
 
118
        return {
 
119
            "required": ssl.CERT_REQUIRED,
 
120
            "none": ssl.CERT_NONE
 
121
            }[unicode_str]
 
122
    except KeyError:
 
123
        raise ValueError("invalid value %s" % unicode_str)
 
124
 
 
125
def default_ca_reqs():
 
126
    if sys.platform in ('win32', 'darwin'):
 
127
        # FIXME: Once we get a native access to root certificates there, this
 
128
        # won't needed anymore. See http://pad.lv/920455 -- vila 2012-02-15
 
129
        return u'none'
 
130
    else:
 
131
        return u'required'
 
132
 
 
133
opt_ssl_ca_certs = config.Option('ssl.ca_certs',
 
134
        from_unicode=ca_certs_from_store,
 
135
        default=default_ca_certs,
 
136
        invalid='warning',
 
137
        help="""\
 
138
Path to certification authority certificates to trust.
 
139
 
 
140
This should be a valid path to a bundle containing all root Certificate
 
141
Authorities used to verify an https server certificate.
 
142
 
 
143
Use ssl.cert_reqs=none to disable certificate verification.
 
144
""")
 
145
 
 
146
opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
 
147
        default=default_ca_reqs,
 
148
        from_unicode=cert_reqs_from_store,
 
149
        invalid='error',
 
150
        help="""\
 
151
Whether to require a certificate from the remote side. (default:required)
 
152
 
 
153
Possible values:
 
154
 * none: Certificates ignored
 
155
 * required: Certificates required and validated
 
156
""")
 
157
 
 
158
checked_kerberos = False
 
159
kerberos = None
 
160
 
 
161
 
 
162
class addinfourl(urllib2.addinfourl):
 
163
    '''Replacement addinfourl class compatible with python-2.7's xmlrpclib
 
164
 
 
165
    In python-2.7, xmlrpclib expects that the response object that it receives
 
166
    has a getheader method.  httplib.HTTPResponse provides this but
 
167
    urllib2.addinfourl does not.  Add the necessary functions here, ported to
 
168
    use the internal data structures of addinfourl.
 
169
    '''
 
170
 
 
171
    def getheader(self, name, default=None):
 
172
        if self.headers is None:
 
173
            raise httplib.ResponseNotReady()
 
174
        return self.headers.getheader(name, default)
 
175
 
 
176
    def getheaders(self):
 
177
        if self.headers is None:
 
178
            raise httplib.ResponseNotReady()
 
179
        return self.headers.items()
76
180
 
77
181
 
78
182
class _ReportingFileSocket(object):
90
194
        self.report_activity(len(s), 'read')
91
195
        return s
92
196
 
93
 
    def readline(self):
94
 
        # This should be readline(self, size=-1), but httplib in python 2.4 and
95
 
        #  2.5 defines a SSLFile wrapper whose readline method lacks the size
96
 
        #  parameter.  So until we drop support for 2.4 and 2.5 and since we
97
 
        #  don't *need* the size parameter we'll stay with readline(self)
98
 
        #  --  vila 20090209
99
 
        s = self.filesock.readline()
 
197
    def readline(self, size=-1):
 
198
        s = self.filesock.readline(size)
100
199
        self.report_activity(len(s), 'read')
101
200
        return s
102
201
 
145
244
    """
146
245
 
147
246
    # Some responses have bodies in which we have no interest
148
 
    _body_ignored_responses = [301,302, 303, 307, 401, 403, 404]
 
247
    _body_ignored_responses = [301,302, 303, 307, 400, 401, 403, 404, 501]
149
248
 
150
249
    # in finish() below, we may have to discard several MB in the worst
151
250
    # case. To avoid buffering that much, we read and discard by chunks
246
345
    def cleanup_pipe(self):
247
346
        """Read the remaining bytes of the last response if any."""
248
347
        if self._response is not None:
249
 
            pending = self._response.finish()
250
 
            # Warn the user (once)
251
 
            if (self._ranges_received_whole_file is None
252
 
                and self._response.status == 200
253
 
                and pending and pending > self._range_warning_thresold
254
 
                ):
255
 
                self._ranges_received_whole_file = True
256
 
                trace.warning(
257
 
                    'Got a 200 response when asking for multiple ranges,'
258
 
                    ' does your server at %s:%s support range requests?',
259
 
                    self.host, self.port)
 
348
            try:
 
349
                pending = self._response.finish()
 
350
                # Warn the user (once)
 
351
                if (self._ranges_received_whole_file is None
 
352
                    and self._response.status == 200
 
353
                    and pending and pending > self._range_warning_thresold
 
354
                    ):
 
355
                    self._ranges_received_whole_file = True
 
356
                    trace.warning(
 
357
                        'Got a 200 response when asking for multiple ranges,'
 
358
                        ' does your server at %s:%s support range requests?',
 
359
                        self.host, self.port)
 
360
            except socket.error, e:
 
361
                # It's conceivable that the socket is in a bad state here
 
362
                # (including some test cases) and in this case, it doesn't need
 
363
                # cleaning anymore, so no need to fail, we just get rid of the
 
364
                # socket and let callers reconnect
 
365
                if (len(e.args) == 0
 
366
                    or e.args[0] not in (errno.ECONNRESET, errno.ECONNABORTED)):
 
367
                    raise
 
368
                self.close()
260
369
            self._response = None
261
370
        # Preserve our preciousss
262
371
        sock = self.sock
275
384
 
276
385
    # XXX: Needs refactoring at the caller level.
277
386
    def __init__(self, host, port=None, proxied_host=None,
278
 
                 report_activity=None):
 
387
                 report_activity=None, ca_certs=None):
279
388
        AbstractHTTPConnection.__init__(self, report_activity=report_activity)
280
389
        # Use strict=True since we don't support HTTP/0.9
281
390
        httplib.HTTPConnection.__init__(self, host, port, strict=True)
282
391
        self.proxied_host = proxied_host
 
392
        # ca_certs is ignored, it's only relevant for https
283
393
 
284
394
    def connect(self):
285
395
        if 'http' in debug.debug_flags:
288
398
        self._wrap_socket_for_reporting(self.sock)
289
399
 
290
400
 
291
 
# Build the appropriate socket wrapper for ssl
292
 
try:
293
 
    # python 2.6 introduced a better ssl package
294
 
    import ssl
295
 
    _ssl_wrap_socket = ssl.wrap_socket
296
 
except ImportError:
297
 
    # python versions prior to 2.6 don't have ssl and ssl.wrap_socket instead
298
 
    # they use httplib.FakeSocket
299
 
    def _ssl_wrap_socket(sock, key_file, cert_file):
300
 
        ssl_sock = socket.ssl(sock, key_file, cert_file)
301
 
        return httplib.FakeSocket(sock, ssl_sock)
 
401
# These two methods were imported from Python 3.2's ssl module
 
402
 
 
403
def _dnsname_to_pat(dn, max_wildcards=1):
 
404
    pats = []
 
405
    for frag in dn.split(r'.'):
 
406
        if frag.count('*') > max_wildcards:
 
407
            # Python Issue #17980: avoid denials of service by refusing more
 
408
            # than one wildcard per fragment.  A survery of established
 
409
            # policy among SSL implementations showed it to be a
 
410
            # reasonable choice.
 
411
            raise ValueError(
 
412
                "too many wildcards in certificate DNS name: " + repr(dn))
 
413
        if frag == '*':
 
414
            # When '*' is a fragment by itself, it matches a non-empty dotless
 
415
            # fragment.
 
416
            pats.append('[^.]+')
 
417
        else:
 
418
            # Otherwise, '*' matches any dotless fragment.
 
419
            frag = re.escape(frag)
 
420
            pats.append(frag.replace(r'\*', '[^.]*'))
 
421
    return re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE)
 
422
 
 
423
 
 
424
def match_hostname(cert, hostname):
 
425
    """Verify that *cert* (in decoded format as returned by
 
426
    SSLSocket.getpeercert()) matches the *hostname*.  RFC 2818 rules
 
427
    are mostly followed, but IP addresses are not accepted for *hostname*.
 
428
 
 
429
    CertificateError is raised on failure. On success, the function
 
430
    returns nothing.
 
431
    """
 
432
    if not cert:
 
433
        raise ValueError("empty or no certificate")
 
434
    dnsnames = []
 
435
    san = cert.get('subjectAltName', ())
 
436
    for key, value in san:
 
437
        if key == 'DNS':
 
438
            if _dnsname_to_pat(value).match(hostname):
 
439
                return
 
440
            dnsnames.append(value)
 
441
    if not san:
 
442
        # The subject is only checked when subjectAltName is empty
 
443
        for sub in cert.get('subject', ()):
 
444
            for key, value in sub:
 
445
                # XXX according to RFC 2818, the most specific Common Name
 
446
                # must be used.
 
447
                if key == 'commonName':
 
448
                    if _dnsname_to_pat(value).match(hostname):
 
449
                        return
 
450
                    dnsnames.append(value)
 
451
    if len(dnsnames) > 1:
 
452
        raise errors.CertificateError(
 
453
            "hostname %r doesn't match either of %s"
 
454
            % (hostname, ', '.join(map(repr, dnsnames))))
 
455
    elif len(dnsnames) == 1:
 
456
        raise errors.CertificateError("hostname %r doesn't match %r" %
 
457
                                      (hostname, dnsnames[0]))
 
458
    else:
 
459
        raise errors.CertificateError("no appropriate commonName or "
 
460
            "subjectAltName fields were found")
302
461
 
303
462
 
304
463
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
305
464
 
306
465
    def __init__(self, host, port=None, key_file=None, cert_file=None,
307
466
                 proxied_host=None,
308
 
                 report_activity=None):
 
467
                 report_activity=None, ca_certs=None):
309
468
        AbstractHTTPConnection.__init__(self, report_activity=report_activity)
310
469
        # Use strict=True since we don't support HTTP/0.9
311
470
        httplib.HTTPSConnection.__init__(self, host, port,
312
471
                                         key_file, cert_file, strict=True)
313
472
        self.proxied_host = proxied_host
 
473
        self.ca_certs = ca_certs
314
474
 
315
475
    def connect(self):
316
476
        if 'http' in debug.debug_flags:
321
481
            self.connect_to_origin()
322
482
 
323
483
    def connect_to_origin(self):
324
 
        ssl_sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
 
484
        # FIXME JRV 2011-12-18: Use location config here?
 
485
        config_stack = config.GlobalStack()
 
486
        cert_reqs = config_stack.get('ssl.cert_reqs')
 
487
        if self.proxied_host is not None:
 
488
            host = self.proxied_host.split(":", 1)[0]
 
489
        else:
 
490
            host = self.host
 
491
        if cert_reqs == ssl.CERT_NONE:
 
492
            ui.ui_factory.show_user_warning('not_checking_ssl_cert', host=host)
 
493
            ui.ui_factory.suppressed_warnings.add('not_checking_ssl_cert')
 
494
            ca_certs = None
 
495
        else:
 
496
            if self.ca_certs is None:
 
497
                ca_certs = config_stack.get('ssl.ca_certs')
 
498
            else:
 
499
                ca_certs = self.ca_certs
 
500
            if ca_certs is None:
 
501
                trace.warning(
 
502
                    "No valid trusted SSL CA certificates file set. See "
 
503
                    "'bzr help ssl.ca_certs' for more information on setting "
 
504
                    "trusted CAs.")
 
505
        try:
 
506
            ssl_sock = ssl.wrap_socket(self.sock, self.key_file, self.cert_file,
 
507
                cert_reqs=cert_reqs, ca_certs=ca_certs)
 
508
        except ssl.SSLError, e:
 
509
            trace.note(
 
510
                "\n"
 
511
                "See `bzr help ssl.ca_certs` for how to specify trusted CA"
 
512
                "certificates.\n"
 
513
                "Pass -Ossl.cert_reqs=none to disable certificate "
 
514
                "verification entirely.\n")
 
515
            raise
 
516
        if cert_reqs == ssl.CERT_REQUIRED:
 
517
            peer_cert = ssl_sock.getpeercert()
 
518
            match_hostname(peer_cert, host)
 
519
 
325
520
        # Wrap the ssl socket before anybody use it
326
521
        self._wrap_socket_for_reporting(ssl_sock)
327
522
 
429
624
 
430
625
    handler_order = 1000 # after all pre-processings
431
626
 
432
 
    def __init__(self, report_activity=None):
 
627
    def __init__(self, report_activity=None, ca_certs=None):
433
628
        self._report_activity = report_activity
 
629
        self.ca_certs = ca_certs
434
630
 
435
631
    def create_connection(self, request, http_connection_class):
436
632
        host = request.get_host()
444
640
        try:
445
641
            connection = http_connection_class(
446
642
                host, proxied_host=request.proxied_host,
447
 
                report_activity=self._report_activity)
 
643
                report_activity=self._report_activity,
 
644
                ca_certs=self.ca_certs)
448
645
        except httplib.InvalidURL, exception:
449
646
            # There is only one occurrence of InvalidURL in httplib
450
647
            raise errors.InvalidURL(request.get_full_url(),
564
761
                        'Bad status line received',
565
762
                        orig_error=exc_val)
566
763
                elif (isinstance(exc_val, socket.error) and len(exc_val.args)
567
 
                      and exc_val.args[0] in (errno.ECONNRESET, 10054)):
 
764
                      and exc_val.args[0] in (errno.ECONNRESET, 10053, 10054)):
 
765
                      # 10053 == WSAECONNABORTED
 
766
                      # 10054 == WSAECONNRESET
568
767
                    raise errors.ConnectionReset(
569
768
                        "Connection lost while sending request.")
570
769
                else:
621
820
                                     headers)
622
821
            if 'http' in debug.debug_flags:
623
822
                trace.mutter('> %s %s' % (method, url))
624
 
                hdrs = ['%s: %s' % (k, v) for k,v in headers.items()]
 
823
                hdrs = []
 
824
                for k,v in headers.iteritems():
 
825
                    # People are often told to paste -Dhttp output to help
 
826
                    # debug. Don't compromise credentials.
 
827
                    if k in ('Authorization', 'Proxy-Authorization'):
 
828
                        v = '<masked>'
 
829
                    hdrs.append('%s: %s' % (k, v))
625
830
                trace.mutter('> ' + '\n> '.join(hdrs) + '\n')
626
831
            if self._debuglevel >= 1:
627
832
                print 'Request sent: [%r] from (%s)' \
628
833
                    % (request, request.connection.sock.getsockname())
629
834
            response = connection.getresponse()
630
835
            convert_to_addinfourl = True
 
836
        except (ssl.SSLError, errors.CertificateError):
 
837
            # Something is wrong with either the certificate or the hostname,
 
838
            # re-trying won't help
 
839
            raise
631
840
        except (socket.gaierror, httplib.BadStatusLine, httplib.UnknownProtocol,
632
841
                socket.error, httplib.HTTPException):
633
842
            response = self.retry_or_raise(http_class, request, first_try)
656
865
            r = response
657
866
            r.recv = r.read
658
867
            fp = socket._fileobject(r, bufsize=65536)
659
 
            resp = urllib2.addinfourl(fp, r.msg, req.get_full_url())
 
868
            resp = addinfourl(fp, r.msg, req.get_full_url())
660
869
            resp.code = r.status
661
870
            resp.msg = r.reason
662
871
            resp.version = r.version
906
1115
        return None
907
1116
 
908
1117
    def proxy_bypass(self, host):
909
 
        """Check if host should be proxied or not"""
 
1118
        """Check if host should be proxied or not.
 
1119
 
 
1120
        :returns: True to skip the proxy, False otherwise.
 
1121
        """
910
1122
        no_proxy = self.get_proxy_env_var('no', default_to=None)
 
1123
        bypass = self.evaluate_proxy_bypass(host, no_proxy)
 
1124
        if bypass is None:
 
1125
            # Nevertheless, there are platform-specific ways to
 
1126
            # ignore proxies...
 
1127
            return urllib.proxy_bypass(host)
 
1128
        else:
 
1129
            return bypass
 
1130
 
 
1131
    def evaluate_proxy_bypass(self, host, no_proxy):
 
1132
        """Check the host against a comma-separated no_proxy list as a string.
 
1133
 
 
1134
        :param host: ``host:port`` being requested
 
1135
 
 
1136
        :param no_proxy: comma-separated list of hosts to access directly.
 
1137
 
 
1138
        :returns: True to skip the proxy, False not to, or None to
 
1139
            leave it to urllib.
 
1140
        """
911
1141
        if no_proxy is None:
 
1142
            # All hosts are proxied
912
1143
            return False
913
1144
        hhost, hport = urllib.splitport(host)
914
1145
        # Does host match any of the domains mentioned in
916
1147
        # are fuzzy (to say the least). We try to allow most
917
1148
        # commonly seen values.
918
1149
        for domain in no_proxy.split(','):
 
1150
            domain = domain.strip()
 
1151
            if domain == '':
 
1152
                continue
919
1153
            dhost, dport = urllib.splitport(domain)
920
1154
            if hport == dport or dport is None:
921
1155
                # Protect glob chars
924
1158
                dhost = dhost.replace("?", r".")
925
1159
                if re.match(dhost, hhost, re.IGNORECASE):
926
1160
                    return True
927
 
        # Nevertheless, there are platform-specific ways to
928
 
        # ignore proxies...
929
 
        return urllib.proxy_bypass(host)
 
1161
        # Nothing explicitly avoid the host
 
1162
        return None
930
1163
 
931
1164
    def set_proxy(self, request, type):
932
1165
        if self.proxy_bypass(request.get_host()):
939
1172
        # grok user:password@host:port as well as
940
1173
        # http://user:password@host:port
941
1174
 
942
 
        (scheme, user, password,
943
 
         host, port, path) = transport.ConnectedTransport._split_url(proxy)
944
 
        if not host:
 
1175
        parsed_url = transport.ConnectedTransport._split_url(proxy)
 
1176
        if not parsed_url.host:
945
1177
            raise errors.InvalidURL(proxy, 'No host component')
946
1178
 
947
1179
        if request.proxy_auth == {}:
949
1181
            # proxied request, intialize.  scheme (the authentication scheme)
950
1182
            # and realm will be set by the AuthHandler
951
1183
            request.proxy_auth = {
952
 
                                  'host': host, 'port': port,
953
 
                                  'user': user, 'password': password,
954
 
                                  'protocol': scheme,
 
1184
                                  'host': parsed_url.host,
 
1185
                                  'port': parsed_url.port,
 
1186
                                  'user': parsed_url.user,
 
1187
                                  'password': parsed_url.password,
 
1188
                                  'protocol': parsed_url.scheme,
955
1189
                                   # We ignore path since we connect to a proxy
956
1190
                                  'path': None}
957
 
        if port is None:
958
 
            phost = host
 
1191
        if parsed_url.port is None:
 
1192
            phost = parsed_url.host
959
1193
        else:
960
 
            phost = host + ':%d' % port
 
1194
            phost = parsed_url.host + ':%d' % parsed_url.port
961
1195
        request.set_proxy(phost, type)
962
1196
        if self._debuglevel >= 3:
963
1197
            print 'set_proxy: proxy set to %s://%s' % (type, phost)
1072
1306
        auth['modified'] = False
1073
1307
        # Put some common info in auth if the caller didn't
1074
1308
        if auth.get('path', None) is None:
1075
 
            (protocol, _, _,
1076
 
             host, port, path) = urlutils.parse_url(request.get_full_url())
1077
 
            self.update_auth(auth, 'protocol', protocol)
1078
 
            self.update_auth(auth, 'host', host)
1079
 
            self.update_auth(auth, 'port', port)
1080
 
            self.update_auth(auth, 'path', path)
 
1309
            parsed_url = urlutils.URL.from_string(request.get_full_url())
 
1310
            self.update_auth(auth, 'protocol', parsed_url.scheme)
 
1311
            self.update_auth(auth, 'host', parsed_url.host)
 
1312
            self.update_auth(auth, 'port', parsed_url.port)
 
1313
            self.update_auth(auth, 'path', parsed_url.path)
1081
1314
        # FIXME: the auth handler should be selected at a single place instead
1082
1315
        # of letting all handlers try to match all headers, but the current
1083
1316
        # design doesn't allow a simple implementation.
1182
1415
        user = auth.get('user', None)
1183
1416
        password = auth.get('password', None)
1184
1417
        realm = auth['realm']
 
1418
        port = auth.get('port', None)
1185
1419
 
1186
1420
        if user is None:
1187
1421
            user = auth_conf.get_user(auth['protocol'], auth['host'],
1188
 
                                      port=auth['port'], path=auth['path'],
 
1422
                                      port=port, path=auth['path'],
1189
1423
                                      realm=realm, ask=True,
1190
1424
                                      prompt=self.build_username_prompt(auth))
1191
1425
        if user is not None and password is None:
1192
1426
            password = auth_conf.get_password(
1193
 
                auth['protocol'], auth['host'], user, port=auth['port'],
 
1427
                auth['protocol'], auth['host'], user,
 
1428
                port=port,
1194
1429
                path=auth['path'], realm=realm,
1195
1430
                prompt=self.build_password_prompt(auth))
1196
1431
 
1207
1442
        user. The daughter classes should implements a public
1208
1443
        build_password_prompt using this method.
1209
1444
        """
1210
 
        prompt = '%s' % auth['protocol'].upper() + ' %(user)s@%(host)s'
 
1445
        prompt = u'%s' % auth['protocol'].upper() + u' %(user)s@%(host)s'
1211
1446
        realm = auth['realm']
1212
1447
        if realm is not None:
1213
 
            prompt += ", Realm: '%s'" % realm
1214
 
        prompt += ' password'
 
1448
            prompt += u", Realm: '%s'" % realm.decode('utf8')
 
1449
        prompt += u' password'
1215
1450
        return prompt
1216
1451
 
1217
1452
    def _build_username_prompt(self, auth):
1225
1460
        user. The daughter classes should implements a public
1226
1461
        build_username_prompt using this method.
1227
1462
        """
1228
 
        prompt = '%s' % auth['protocol'].upper() + ' %(host)s'
 
1463
        prompt = u'%s' % auth['protocol'].upper() + u' %(host)s'
1229
1464
        realm = auth['realm']
1230
1465
        if realm is not None:
1231
 
            prompt += ", Realm: '%s'" % realm
1232
 
        prompt += ' username'
 
1466
            prompt += u", Realm: '%s'" % realm.decode('utf8')
 
1467
        prompt += u' username'
1233
1468
        return prompt
1234
1469
 
1235
1470
    def http_request(self, request):
1267
1502
 
1268
1503
    def _auth_match_kerberos(self, auth):
1269
1504
        """Try to create a GSSAPI response for authenticating against a host."""
1270
 
        if not have_kerberos:
 
1505
        global kerberos, checked_kerberos
 
1506
        if kerberos is None and not checked_kerberos:
 
1507
            try:
 
1508
                import kerberos
 
1509
            except ImportError:
 
1510
                kerberos = None
 
1511
            checked_kerberos = True
 
1512
        if kerberos is None:
1271
1513
            return None
1272
1514
        ret, vc = kerberos.authGSSClientInit("HTTP@%(host)s" % auth)
1273
1515
        if ret < 1:
1340
1582
    if algorithm == 'MD5':
1341
1583
        H = lambda x: osutils.md5(x).hexdigest()
1342
1584
    elif algorithm == 'SHA':
1343
 
        H = lambda x: osutils.sha(x).hexdigest()
 
1585
        H = osutils.sha_string
1344
1586
    if H is not None:
1345
1587
        KD = lambda secret, data: H("%s:%s" % (secret, data))
1346
1588
    return H, KD
1349
1591
def get_new_cnonce(nonce, nonce_count):
1350
1592
    raw = '%s:%d:%s:%s' % (nonce, nonce_count, time.ctime(),
1351
1593
                           urllib2.randombytes(8))
1352
 
    return osutils.sha(raw).hexdigest()[:16]
 
1594
    return osutils.sha_string(raw)[:16]
1353
1595
 
1354
1596
 
1355
1597
class DigestAuthHandler(AbstractAuthHandler):
1499
1741
 
1500
1742
    def build_password_prompt(self, auth):
1501
1743
        prompt = self._build_password_prompt(auth)
1502
 
        prompt = 'Proxy ' + prompt
 
1744
        prompt = u'Proxy ' + prompt
1503
1745
        return prompt
1504
1746
 
1505
1747
    def build_username_prompt(self, auth):
1506
1748
        prompt = self._build_username_prompt(auth)
1507
 
        prompt = 'Proxy ' + prompt
 
1749
        prompt = u'Proxy ' + prompt
1508
1750
        return prompt
1509
1751
 
1510
1752
    def http_error_407(self, req, fp, code, msg, headers):
1592
1834
                 connection=ConnectionHandler,
1593
1835
                 redirect=HTTPRedirectHandler,
1594
1836
                 error=HTTPErrorProcessor,
1595
 
                 report_activity=None):
 
1837
                 report_activity=None,
 
1838
                 ca_certs=None):
1596
1839
        self._opener = urllib2.build_opener(
1597
 
            connection(report_activity=report_activity),
 
1840
            connection(report_activity=report_activity, ca_certs=ca_certs),
1598
1841
            redirect, error,
1599
1842
            ProxyHandler(),
1600
1843
            HTTPBasicAuthHandler(),