/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

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

  • Committer: Martin Pool
  • Date: 2009-12-14 06:06:59 UTC
  • mfrom: (4889 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4891.
  • Revision ID: mbp@sourcefrog.net-20091214060659-1ucv8hpnky0cbnaj
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 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
12
12
#
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
16
16
 
17
17
"""Implementaion of urllib2 tailored to bzr needs
18
18
 
46
46
# actual code more or less do that, tests should be written to
47
47
# ensure that.
48
48
 
 
49
import errno
49
50
import httplib
50
 
import md5
51
 
import sha
 
51
try:
 
52
    import kerberos
 
53
except ImportError:
 
54
    have_kerberos = False
 
55
else:
 
56
    have_kerberos = True
52
57
import socket
53
58
import urllib
54
59
import urllib2
62
67
    config,
63
68
    debug,
64
69
    errors,
 
70
    osutils,
65
71
    trace,
66
72
    transport,
67
73
    ui,
 
74
    urlutils,
68
75
    )
69
76
 
70
77
 
71
 
class _BufferedMakefileSocket(object):
72
 
 
73
 
    def __init__(self, sock):
 
78
class _ReportingFileSocket(object):
 
79
 
 
80
    def __init__(self, filesock, report_activity=None):
 
81
        self.filesock = filesock
 
82
        self._report_activity = report_activity
 
83
 
 
84
    def report_activity(self, size, direction):
 
85
        if self._report_activity:
 
86
            self._report_activity(size, direction)
 
87
 
 
88
    def read(self, size=1):
 
89
        s = self.filesock.read(size)
 
90
        self.report_activity(len(s), 'read')
 
91
        return s
 
92
 
 
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()
 
100
        self.report_activity(len(s), 'read')
 
101
        return s
 
102
 
 
103
    def __getattr__(self, name):
 
104
        return getattr(self.filesock, name)
 
105
 
 
106
 
 
107
class _ReportingSocket(object):
 
108
 
 
109
    def __init__(self, sock, report_activity=None):
74
110
        self.sock = sock
 
111
        self._report_activity = report_activity
 
112
 
 
113
    def report_activity(self, size, direction):
 
114
        if self._report_activity:
 
115
            self._report_activity(size, direction)
 
116
 
 
117
    def sendall(self, s, *args):
 
118
        self.sock.sendall(s, *args)
 
119
        self.report_activity(len(s), 'write')
 
120
 
 
121
    def recv(self, *args):
 
122
        s = self.sock.recv(*args)
 
123
        self.report_activity(len(s), 'read')
 
124
        return s
75
125
 
76
126
    def makefile(self, mode='r', bufsize=-1):
77
 
        return self.sock.makefile(mode, 65536)
 
127
        # httplib creates a fileobject that doesn't do buffering, which
 
128
        # makes fp.readline() very expensive because it only reads one byte
 
129
        # at a time.  So we wrap the socket in an object that forces
 
130
        # sock.makefile to make a buffered file.
 
131
        fsock = self.sock.makefile(mode, 65536)
 
132
        # And wrap that into a reporting kind of fileobject
 
133
        return _ReportingFileSocket(fsock, self._report_activity)
78
134
 
79
135
    def __getattr__(self, name):
80
136
        return getattr(self.sock, name)
97
153
    # 8k chunks should be fine.
98
154
    _discarded_buf_size = 8192
99
155
 
100
 
    def __init__(self, sock, *args, **kwargs):
101
 
        # httplib creates a fileobject that doesn't do buffering, which
102
 
        # makes fp.readline() very expensive because it only reads one byte
103
 
        # at a time.  So we wrap the socket in an object that forces
104
 
        # sock.makefile to make a buffered file.
105
 
        sock = _BufferedMakefileSocket(sock)
106
 
        httplib.HTTPResponse.__init__(self, sock, *args, **kwargs)
107
 
 
108
156
    def begin(self):
109
157
        """Begin to read the response from the server.
110
158
 
179
227
    # we want to warn. But not below a given thresold.
180
228
    _range_warning_thresold = 1024 * 1024
181
229
 
182
 
    def __init__(self):
 
230
    def __init__(self, report_activity=None):
183
231
        self._response = None
 
232
        self._report_activity = report_activity
184
233
        self._ranges_received_whole_file = None
185
234
 
186
235
    def _mutter_connect(self):
212
261
        # Preserve our preciousss
213
262
        sock = self.sock
214
263
        self.sock = None
215
 
        # Let httplib.HTTPConnection do its housekeeping 
 
264
        # Let httplib.HTTPConnection do its housekeeping
216
265
        self.close()
217
266
        # Restore our preciousss
218
267
        self.sock = sock
219
268
 
 
269
    def _wrap_socket_for_reporting(self, sock):
 
270
        """Wrap the socket before anybody use it."""
 
271
        self.sock = _ReportingSocket(sock, self._report_activity)
 
272
 
220
273
 
221
274
class HTTPConnection(AbstractHTTPConnection, httplib.HTTPConnection):
222
275
 
223
276
    # XXX: Needs refactoring at the caller level.
224
 
    def __init__(self, host, port=None, proxied_host=None):
225
 
        AbstractHTTPConnection.__init__(self)
 
277
    def __init__(self, host, port=None, proxied_host=None,
 
278
                 report_activity=None):
 
279
        AbstractHTTPConnection.__init__(self, report_activity=report_activity)
226
280
        # Use strict=True since we don't support HTTP/0.9
227
281
        httplib.HTTPConnection.__init__(self, host, port, strict=True)
228
282
        self.proxied_host = proxied_host
231
285
        if 'http' in debug.debug_flags:
232
286
            self._mutter_connect()
233
287
        httplib.HTTPConnection.connect(self)
234
 
 
235
 
 
236
 
# FIXME: Should test for ssl availability
 
288
        self._wrap_socket_for_reporting(self.sock)
 
289
 
 
290
 
 
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)
 
302
 
 
303
 
237
304
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
238
305
 
239
306
    def __init__(self, host, port=None, key_file=None, cert_file=None,
240
 
                 proxied_host=None):
241
 
        AbstractHTTPConnection.__init__(self)
 
307
                 proxied_host=None,
 
308
                 report_activity=None):
 
309
        AbstractHTTPConnection.__init__(self, report_activity=report_activity)
242
310
        # Use strict=True since we don't support HTTP/0.9
243
311
        httplib.HTTPSConnection.__init__(self, host, port,
244
312
                                         key_file, cert_file, strict=True)
248
316
        if 'http' in debug.debug_flags:
249
317
            self._mutter_connect()
250
318
        httplib.HTTPConnection.connect(self)
 
319
        self._wrap_socket_for_reporting(self.sock)
251
320
        if self.proxied_host is None:
252
321
            self.connect_to_origin()
253
322
 
254
323
    def connect_to_origin(self):
255
 
        ssl = socket.ssl(self.sock, self.key_file, self.cert_file)
256
 
        self.sock = httplib.FakeSocket(self.sock, ssl)
 
324
        ssl_sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
 
325
        # Wrap the ssl socket before anybody use it
 
326
        self._wrap_socket_for_reporting(ssl_sock)
257
327
 
258
328
 
259
329
class Request(urllib2.Request):
297
367
 
298
368
    def set_proxy(self, proxy, type):
299
369
        """Set the proxy and remember the proxied host."""
300
 
        self.proxied_host = self.get_host()
 
370
        host, port = urllib.splitport(self.get_host())
 
371
        if port is None:
 
372
            # We need to set the default port ourselves way before it gets set
 
373
            # in the HTTP[S]Connection object at build time.
 
374
            if self.type == 'https':
 
375
                conn_class = HTTPSConnection
 
376
            else:
 
377
                conn_class = HTTPConnection
 
378
            port = conn_class.default_port
 
379
        self.proxied_host = '%s:%s' % (host, port)
301
380
        urllib2.Request.set_proxy(self, proxy, type)
302
381
 
303
382
 
305
384
 
306
385
    def __init__(self, request):
307
386
        """Constructor
308
 
        
 
387
 
309
388
        :param request: the first request sent to the proxied host, already
310
389
            processed by the opener (i.e. proxied_host is already set).
311
390
        """
345
424
 
346
425
    handler_order = 1000 # after all pre-processings
347
426
 
 
427
    def __init__(self, report_activity=None):
 
428
        self._report_activity = report_activity
 
429
 
348
430
    def create_connection(self, request, http_connection_class):
349
431
        host = request.get_host()
350
432
        if not host:
356
438
        # request is made)
357
439
        try:
358
440
            connection = http_connection_class(
359
 
                host, proxied_host=request.proxied_host)
 
441
                host, proxied_host=request.proxied_host,
 
442
                report_activity=self._report_activity)
360
443
        except httplib.InvalidURL, exception:
361
444
            # There is only one occurrence of InvalidURL in httplib
362
445
            raise errors.InvalidURL(request.get_full_url(),
475
558
                        request.get_full_url(),
476
559
                        'Bad status line received',
477
560
                        orig_error=exc_val)
 
561
                elif (isinstance(exc_val, socket.error) and len(exc_val.args)
 
562
                      and exc_val.args[0] in (errno.ECONNRESET, 10054)):
 
563
                    raise errors.ConnectionReset(
 
564
                        "Connection lost while sending request.")
478
565
                else:
479
566
                    # All other exception are considered connection related.
480
567
 
628
715
                        connect.proxied_host, self.host))
629
716
            # Housekeeping
630
717
            connection.cleanup_pipe()
631
 
            # Establish the connection encryption 
 
718
            # Establish the connection encryption
632
719
            connection.connect_to_origin()
633
720
            # Propagate the connection to the original request
634
721
            request.connection = connection
851
938
 
852
939
        (scheme, user, password,
853
940
         host, port, path) = transport.ConnectedTransport._split_url(proxy)
 
941
        if not host:
 
942
            raise errors.InvalidURL(proxy, 'No host component')
854
943
 
855
944
        if request.proxy_auth == {}:
856
945
            # No proxy auth parameter are available, we are handling the first
879
968
    preventively set authentication headers after the first
880
969
    successful authentication.
881
970
 
882
 
    This can be used for http and proxy, as well as for basic and
 
971
    This can be used for http and proxy, as well as for basic, negotiate and
883
972
    digest authentications.
884
973
 
885
974
    This provides an unified interface for all authentication handlers
910
999
      successful and the request authentication parameters have been updated.
911
1000
    """
912
1001
 
 
1002
    scheme = None
 
1003
    """The scheme as it appears in the server header (lower cased)"""
 
1004
 
913
1005
    _max_retry = 3
914
1006
    """We don't want to retry authenticating endlessly"""
915
1007
 
 
1008
    requires_username = True
 
1009
    """Whether the auth mechanism requires a username."""
 
1010
 
916
1011
    # The following attributes should be defined by daughter
917
1012
    # classes:
918
1013
    # - auth_required_header:  the header received from the server
924
1019
        # in such a cycle by default.
925
1020
        self._retry_count = None
926
1021
 
 
1022
    def _parse_auth_header(self, server_header):
 
1023
        """Parse the authentication header.
 
1024
 
 
1025
        :param server_header: The value of the header sent by the server
 
1026
            describing the authenticaion request.
 
1027
 
 
1028
        :return: A tuple (scheme, remainder) scheme being the first word in the
 
1029
            given header (lower cased), remainder may be None.
 
1030
        """
 
1031
        try:
 
1032
            scheme, remainder = server_header.split(None, 1)
 
1033
        except ValueError:
 
1034
            scheme = server_header
 
1035
            remainder = None
 
1036
        return (scheme.lower(), remainder)
 
1037
 
927
1038
    def update_auth(self, auth, key, value):
928
1039
        """Update a value in auth marking the auth as modified if needed"""
929
1040
        old_value = auth.get(key, None)
948
1059
                # Let's be ready for next round
949
1060
                self._retry_count = None
950
1061
                return None
951
 
        server_header = headers.get(self.auth_required_header, None)
952
 
        if server_header is None:
 
1062
        server_headers = headers.getheaders(self.auth_required_header)
 
1063
        if not server_headers:
953
1064
            # The http error MUST have the associated
954
1065
            # header. This must never happen in production code.
955
1066
            raise KeyError('%s not found' % self.auth_required_header)
956
1067
 
957
1068
        auth = self.get_auth(request)
958
 
        if auth.get('user', None) is None:
959
 
            # Without a known user, we can't authenticate
960
 
            return None
961
 
 
962
1069
        auth['modified'] = False
963
 
        if self.auth_match(server_header, auth):
964
 
            # auth_match may have modified auth (by adding the
965
 
            # password or changing the realm, for example)
966
 
            if (request.get_header(self.auth_header, None) is not None
967
 
                and not auth['modified']):
968
 
                # We already tried that, give up
969
 
                return None
970
 
 
971
 
            # Housekeeping
972
 
            request.connection.cleanup_pipe()
973
 
            response = self.parent.open(request)
974
 
            if response:
975
 
                self.auth_successful(request, response)
976
 
            return response
 
1070
        # Put some common info in auth if the caller didn't
 
1071
        if auth.get('path', None) is None:
 
1072
            (protocol, _, _,
 
1073
             host, port, path) = urlutils.parse_url(request.get_full_url())
 
1074
            self.update_auth(auth, 'protocol', protocol)
 
1075
            self.update_auth(auth, 'host', host)
 
1076
            self.update_auth(auth, 'port', port)
 
1077
            self.update_auth(auth, 'path', path)
 
1078
        # FIXME: the auth handler should be selected at a single place instead
 
1079
        # of letting all handlers try to match all headers, but the current
 
1080
        # design doesn't allow a simple implementation.
 
1081
        for server_header in server_headers:
 
1082
            # Several schemes can be proposed by the server, try to match each
 
1083
            # one in turn
 
1084
            matching_handler = self.auth_match(server_header, auth)
 
1085
            if matching_handler:
 
1086
                # auth_match may have modified auth (by adding the
 
1087
                # password or changing the realm, for example)
 
1088
                if (request.get_header(self.auth_header, None) is not None
 
1089
                    and not auth['modified']):
 
1090
                    # We already tried that, give up
 
1091
                    return None
 
1092
 
 
1093
                # Only the most secure scheme proposed by the server should be
 
1094
                # used, since the handlers use 'handler_order' to describe that
 
1095
                # property, the first handler tried takes precedence, the
 
1096
                # others should not attempt to authenticate if the best one
 
1097
                # failed.
 
1098
                best_scheme = auth.get('best_scheme', None)
 
1099
                if best_scheme is None:
 
1100
                    # At that point, if current handler should doesn't succeed
 
1101
                    # the credentials are wrong (or incomplete), but we know
 
1102
                    # that the associated scheme should be used.
 
1103
                    best_scheme = auth['best_scheme'] = self.scheme
 
1104
                if  best_scheme != self.scheme:
 
1105
                    continue
 
1106
 
 
1107
                if self.requires_username and auth.get('user', None) is None:
 
1108
                    # Without a known user, we can't authenticate
 
1109
                    return None
 
1110
 
 
1111
                # Housekeeping
 
1112
                request.connection.cleanup_pipe()
 
1113
                # Retry the request with an authentication header added
 
1114
                response = self.parent.open(request)
 
1115
                if response:
 
1116
                    self.auth_successful(request, response)
 
1117
                return response
977
1118
        # We are not qualified to handle the authentication.
978
1119
        # Note: the authentication error handling will try all
979
1120
        # available handlers. If one of them authenticates
999
1140
        (digest's nonce is an example, digest's nonce_count is a
1000
1141
        *counter-example*). Such parameters must be updated by
1001
1142
        using the update_auth() method.
1002
 
        
 
1143
 
1003
1144
        :param header: The authentication header sent by the server.
1004
1145
        :param auth: The auth parameters already known. They may be
1005
1146
             updated.
1029
1170
        self._retry_count = None
1030
1171
 
1031
1172
    def get_user_password(self, auth):
1032
 
        """Ask user for a password if none is already available."""
 
1173
        """Ask user for a password if none is already available.
 
1174
 
 
1175
        :param auth: authentication info gathered so far (from the initial url
 
1176
            and then during dialog with the server).
 
1177
        """
1033
1178
        auth_conf = config.AuthenticationConfig()
1034
 
        user = auth['user']
1035
 
        password = auth['password']
 
1179
        user = auth.get('user', None)
 
1180
        password = auth.get('password', None)
1036
1181
        realm = auth['realm']
1037
1182
 
1038
1183
        if user is None:
1039
 
            user = auth.get_user(auth['protocol'], auth['host'],
1040
 
                                 port=auth['port'], path=auth['path'],
1041
 
                                 realm=realm)
1042
 
            if user is None:
1043
 
                # Default to local user
1044
 
                user = getpass.getuser()
1045
 
 
1046
 
        if password is None:
 
1184
            user = auth_conf.get_user(auth['protocol'], auth['host'],
 
1185
                                      port=auth['port'], path=auth['path'],
 
1186
                                      realm=realm, ask=True,
 
1187
                                      prompt=self.build_username_prompt(auth))
 
1188
        if user is not None and password is None:
1047
1189
            password = auth_conf.get_password(
1048
1190
                auth['protocol'], auth['host'], user, port=auth['port'],
1049
1191
                path=auth['path'], realm=realm,
1069
1211
        prompt += ' password'
1070
1212
        return prompt
1071
1213
 
 
1214
    def _build_username_prompt(self, auth):
 
1215
        """Build a prompt taking the protocol used into account.
 
1216
 
 
1217
        The AuthHandler is used by http and https, we want that information in
 
1218
        the prompt, so we build the prompt from the authentication dict which
 
1219
        contains all the needed parts.
 
1220
 
 
1221
        Also, http and proxy AuthHandlers present different prompts to the
 
1222
        user. The daughter classes should implements a public
 
1223
        build_username_prompt using this method.
 
1224
        """
 
1225
        prompt = '%s' % auth['protocol'].upper() + ' %(host)s'
 
1226
        realm = auth['realm']
 
1227
        if realm is not None:
 
1228
            prompt += ", Realm: '%s'" % realm
 
1229
        prompt += ' username'
 
1230
        return prompt
 
1231
 
1072
1232
    def http_request(self, request):
1073
1233
        """Insert an authentication header if information is available"""
1074
1234
        auth = self.get_auth(request)
1079
1239
    https_request = http_request # FIXME: Need test
1080
1240
 
1081
1241
 
 
1242
class NegotiateAuthHandler(AbstractAuthHandler):
 
1243
    """A authentication handler that handles WWW-Authenticate: Negotiate.
 
1244
 
 
1245
    At the moment this handler supports just Kerberos. In the future,
 
1246
    NTLM support may also be added.
 
1247
    """
 
1248
 
 
1249
    scheme = 'negotiate'
 
1250
    handler_order = 480
 
1251
    requires_username = False
 
1252
 
 
1253
    def auth_match(self, header, auth):
 
1254
        scheme, raw_auth = self._parse_auth_header(header)
 
1255
        if scheme != self.scheme:
 
1256
            return False
 
1257
        self.update_auth(auth, 'scheme', scheme)
 
1258
        resp = self._auth_match_kerberos(auth)
 
1259
        if resp is None:
 
1260
            return False
 
1261
        # Optionally should try to authenticate using NTLM here
 
1262
        self.update_auth(auth, 'negotiate_response', resp)
 
1263
        return True
 
1264
 
 
1265
    def _auth_match_kerberos(self, auth):
 
1266
        """Try to create a GSSAPI response for authenticating against a host."""
 
1267
        if not have_kerberos:
 
1268
            return None
 
1269
        ret, vc = kerberos.authGSSClientInit("HTTP@%(host)s" % auth)
 
1270
        if ret < 1:
 
1271
            trace.warning('Unable to create GSSAPI context for %s: %d',
 
1272
                auth['host'], ret)
 
1273
            return None
 
1274
        ret = kerberos.authGSSClientStep(vc, "")
 
1275
        if ret < 0:
 
1276
            trace.mutter('authGSSClientStep failed: %d', ret)
 
1277
            return None
 
1278
        return kerberos.authGSSClientResponse(vc)
 
1279
 
 
1280
    def build_auth_header(self, auth, request):
 
1281
        return "Negotiate %s" % auth['negotiate_response']
 
1282
 
 
1283
    def auth_params_reusable(self, auth):
 
1284
        # If the auth scheme is known, it means a previous
 
1285
        # authentication was successful, all information is
 
1286
        # available, no further checks are needed.
 
1287
        return (auth.get('scheme', None) == 'negotiate' and
 
1288
                auth.get('negotiate_response', None) is not None)
 
1289
 
 
1290
 
1082
1291
class BasicAuthHandler(AbstractAuthHandler):
1083
1292
    """A custom basic authentication handler."""
1084
1293
 
 
1294
    scheme = 'basic'
1085
1295
    handler_order = 500
1086
 
 
1087
1296
    auth_regexp = re.compile('realm="([^"]*)"', re.I)
1088
1297
 
1089
1298
    def build_auth_header(self, auth, request):
1091
1300
        auth_header = 'Basic ' + raw.encode('base64').strip()
1092
1301
        return auth_header
1093
1302
 
 
1303
    def extract_realm(self, header_value):
 
1304
        match = self.auth_regexp.search(header_value)
 
1305
        realm = None
 
1306
        if match:
 
1307
            realm = match.group(1)
 
1308
        return match, realm
 
1309
 
1094
1310
    def auth_match(self, header, auth):
1095
 
        scheme, raw_auth = header.split(None, 1)
1096
 
        scheme = scheme.lower()
1097
 
        if scheme != 'basic':
 
1311
        scheme, raw_auth = self._parse_auth_header(header)
 
1312
        if scheme != self.scheme:
1098
1313
            return False
1099
1314
 
1100
 
        match = self.auth_regexp.search(raw_auth)
 
1315
        match, realm = self.extract_realm(raw_auth)
1101
1316
        if match:
1102
 
            realm = match.groups()
1103
 
            if scheme != 'basic':
1104
 
                return False
1105
 
 
1106
1317
            # Put useful info into auth
1107
1318
            self.update_auth(auth, 'scheme', scheme)
1108
1319
            self.update_auth(auth, 'realm', realm)
1109
 
            if auth['user'] is None or auth['password'] is None:
 
1320
            if (auth.get('user', None) is None
 
1321
                or auth.get('password', None) is None):
1110
1322
                user, password = self.get_user_password(auth)
1111
1323
                self.update_auth(auth, 'user', user)
1112
1324
                self.update_auth(auth, 'password', password)
1123
1335
    H = None
1124
1336
    KD = None
1125
1337
    if algorithm == 'MD5':
1126
 
        H = lambda x: md5.new(x).hexdigest()
 
1338
        H = lambda x: osutils.md5(x).hexdigest()
1127
1339
    elif algorithm == 'SHA':
1128
 
        H = lambda x: sha.new(x).hexdigest()
 
1340
        H = lambda x: osutils.sha(x).hexdigest()
1129
1341
    if H is not None:
1130
1342
        KD = lambda secret, data: H("%s:%s" % (secret, data))
1131
1343
    return H, KD
1134
1346
def get_new_cnonce(nonce, nonce_count):
1135
1347
    raw = '%s:%d:%s:%s' % (nonce, nonce_count, time.ctime(),
1136
1348
                           urllib2.randombytes(8))
1137
 
    return sha.new(raw).hexdigest()[:16]
 
1349
    return osutils.sha(raw).hexdigest()[:16]
1138
1350
 
1139
1351
 
1140
1352
class DigestAuthHandler(AbstractAuthHandler):
1141
1353
    """A custom digest authentication handler."""
1142
1354
 
1143
 
    # Before basic as digest is a bit more secure
 
1355
    scheme = 'digest'
 
1356
    # Before basic as digest is a bit more secure and should be preferred
1144
1357
    handler_order = 490
1145
1358
 
1146
1359
    def auth_params_reusable(self, auth):
1150
1363
        return auth.get('scheme', None) == 'digest'
1151
1364
 
1152
1365
    def auth_match(self, header, auth):
1153
 
        scheme, raw_auth = header.split(None, 1)
1154
 
        scheme = scheme.lower()
1155
 
        if scheme != 'digest':
 
1366
        scheme, raw_auth = self._parse_auth_header(header)
 
1367
        if scheme != self.scheme:
1156
1368
            return False
1157
1369
 
1158
1370
        # Put the requested authentication info into a dict
1171
1383
        # Put useful info into auth
1172
1384
        self.update_auth(auth, 'scheme', scheme)
1173
1385
        self.update_auth(auth, 'realm', realm)
1174
 
        if auth['user'] is None or auth['password'] is None:
 
1386
        if auth.get('user', None) is None or auth.get('password', None) is None:
1175
1387
            user, password = self.get_user_password(auth)
1176
1388
            self.update_auth(auth, 'user', user)
1177
1389
            self.update_auth(auth, 'password', password)
1253
1465
    def build_password_prompt(self, auth):
1254
1466
        return self._build_password_prompt(auth)
1255
1467
 
 
1468
    def build_username_prompt(self, auth):
 
1469
        return self._build_username_prompt(auth)
 
1470
 
1256
1471
    def http_error_401(self, req, fp, code, msg, headers):
1257
1472
        return self.auth_required(req, headers)
1258
1473
 
1284
1499
        prompt = 'Proxy ' + prompt
1285
1500
        return prompt
1286
1501
 
 
1502
    def build_username_prompt(self, auth):
 
1503
        prompt = self._build_username_prompt(auth)
 
1504
        prompt = 'Proxy ' + prompt
 
1505
        return prompt
 
1506
 
1287
1507
    def http_error_407(self, req, fp, code, msg, headers):
1288
1508
        return self.auth_required(req, headers)
1289
1509
 
1304
1524
    """Custom proxy basic authentication handler"""
1305
1525
 
1306
1526
 
 
1527
class HTTPNegotiateAuthHandler(NegotiateAuthHandler, HTTPAuthHandler):
 
1528
    """Custom http negotiate authentication handler"""
 
1529
 
 
1530
 
 
1531
class ProxyNegotiateAuthHandler(NegotiateAuthHandler, ProxyAuthHandler):
 
1532
    """Custom proxy negotiate authentication handler"""
 
1533
 
 
1534
 
1307
1535
class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
1308
1536
    """Process HTTP error responses.
1309
1537
 
1360
1588
    def __init__(self,
1361
1589
                 connection=ConnectionHandler,
1362
1590
                 redirect=HTTPRedirectHandler,
1363
 
                 error=HTTPErrorProcessor,):
1364
 
        self._opener = urllib2.build_opener( \
1365
 
            connection, redirect, error,
 
1591
                 error=HTTPErrorProcessor,
 
1592
                 report_activity=None):
 
1593
        self._opener = urllib2.build_opener(
 
1594
            connection(report_activity=report_activity),
 
1595
            redirect, error,
1366
1596
            ProxyHandler(),
1367
1597
            HTTPBasicAuthHandler(),
1368
1598
            HTTPDigestAuthHandler(),
 
1599
            HTTPNegotiateAuthHandler(),
1369
1600
            ProxyBasicAuthHandler(),
1370
1601
            ProxyDigestAuthHandler(),
 
1602
            ProxyNegotiateAuthHandler(),
1371
1603
            HTTPHandler,
1372
1604
            HTTPSHandler,
1373
1605
            HTTPDefaultErrorHandler,