/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 von Gagern
  • Date: 2010-04-20 08:47:38 UTC
  • mfrom: (5167 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5195.
  • Revision ID: martin.vgagern@gmx.net-20100420084738-ygymnqmdllzrhpfn
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-2010 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
 
51
try:
 
52
    import kerberos
 
53
except ImportError:
 
54
    have_kerberos = False
 
55
else:
 
56
    have_kerberos = True
50
57
import socket
51
58
import urllib
52
59
import urllib2
64
71
    trace,
65
72
    transport,
66
73
    ui,
 
74
    urlutils,
67
75
    )
68
76
 
69
77
 
70
 
class _BufferedMakefileSocket(object):
71
 
 
72
 
    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):
73
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
74
125
 
75
126
    def makefile(self, mode='r', bufsize=-1):
76
 
        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)
77
134
 
78
135
    def __getattr__(self, name):
79
136
        return getattr(self.sock, name)
96
153
    # 8k chunks should be fine.
97
154
    _discarded_buf_size = 8192
98
155
 
99
 
    def __init__(self, sock, *args, **kwargs):
100
 
        # httplib creates a fileobject that doesn't do buffering, which
101
 
        # makes fp.readline() very expensive because it only reads one byte
102
 
        # at a time.  So we wrap the socket in an object that forces
103
 
        # sock.makefile to make a buffered file.
104
 
        sock = _BufferedMakefileSocket(sock)
105
 
        httplib.HTTPResponse.__init__(self, sock, *args, **kwargs)
106
 
 
107
156
    def begin(self):
108
157
        """Begin to read the response from the server.
109
158
 
178
227
    # we want to warn. But not below a given thresold.
179
228
    _range_warning_thresold = 1024 * 1024
180
229
 
181
 
    def __init__(self):
 
230
    def __init__(self, report_activity=None):
182
231
        self._response = None
 
232
        self._report_activity = report_activity
183
233
        self._ranges_received_whole_file = None
184
234
 
185
235
    def _mutter_connect(self):
211
261
        # Preserve our preciousss
212
262
        sock = self.sock
213
263
        self.sock = None
214
 
        # Let httplib.HTTPConnection do its housekeeping 
 
264
        # Let httplib.HTTPConnection do its housekeeping
215
265
        self.close()
216
266
        # Restore our preciousss
217
267
        self.sock = sock
218
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
 
219
273
 
220
274
class HTTPConnection(AbstractHTTPConnection, httplib.HTTPConnection):
221
275
 
222
276
    # XXX: Needs refactoring at the caller level.
223
 
    def __init__(self, host, port=None, proxied_host=None):
224
 
        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)
225
280
        # Use strict=True since we don't support HTTP/0.9
226
281
        httplib.HTTPConnection.__init__(self, host, port, strict=True)
227
282
        self.proxied_host = proxied_host
230
285
        if 'http' in debug.debug_flags:
231
286
            self._mutter_connect()
232
287
        httplib.HTTPConnection.connect(self)
233
 
 
234
 
 
235
 
# 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
 
236
304
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
237
305
 
238
306
    def __init__(self, host, port=None, key_file=None, cert_file=None,
239
 
                 proxied_host=None):
240
 
        AbstractHTTPConnection.__init__(self)
 
307
                 proxied_host=None,
 
308
                 report_activity=None):
 
309
        AbstractHTTPConnection.__init__(self, report_activity=report_activity)
241
310
        # Use strict=True since we don't support HTTP/0.9
242
311
        httplib.HTTPSConnection.__init__(self, host, port,
243
312
                                         key_file, cert_file, strict=True)
247
316
        if 'http' in debug.debug_flags:
248
317
            self._mutter_connect()
249
318
        httplib.HTTPConnection.connect(self)
 
319
        self._wrap_socket_for_reporting(self.sock)
250
320
        if self.proxied_host is None:
251
321
            self.connect_to_origin()
252
322
 
253
323
    def connect_to_origin(self):
254
 
        ssl = socket.ssl(self.sock, self.key_file, self.cert_file)
255
 
        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)
256
327
 
257
328
 
258
329
class Request(urllib2.Request):
296
367
 
297
368
    def set_proxy(self, proxy, type):
298
369
        """Set the proxy and remember the proxied host."""
299
 
        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)
300
380
        urllib2.Request.set_proxy(self, proxy, type)
301
381
 
302
382
 
304
384
 
305
385
    def __init__(self, request):
306
386
        """Constructor
307
 
        
 
387
 
308
388
        :param request: the first request sent to the proxied host, already
309
389
            processed by the opener (i.e. proxied_host is already set).
310
390
        """
344
424
 
345
425
    handler_order = 1000 # after all pre-processings
346
426
 
 
427
    def __init__(self, report_activity=None):
 
428
        self._report_activity = report_activity
 
429
 
347
430
    def create_connection(self, request, http_connection_class):
348
431
        host = request.get_host()
349
432
        if not host:
355
438
        # request is made)
356
439
        try:
357
440
            connection = http_connection_class(
358
 
                host, proxied_host=request.proxied_host)
 
441
                host, proxied_host=request.proxied_host,
 
442
                report_activity=self._report_activity)
359
443
        except httplib.InvalidURL, exception:
360
444
            # There is only one occurrence of InvalidURL in httplib
361
445
            raise errors.InvalidURL(request.get_full_url(),
474
558
                        request.get_full_url(),
475
559
                        'Bad status line received',
476
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.")
477
565
                else:
478
566
                    # All other exception are considered connection related.
479
567
 
627
715
                        connect.proxied_host, self.host))
628
716
            # Housekeeping
629
717
            connection.cleanup_pipe()
630
 
            # Establish the connection encryption 
 
718
            # Establish the connection encryption
631
719
            connection.connect_to_origin()
632
720
            # Propagate the connection to the original request
633
721
            request.connection = connection
850
938
 
851
939
        (scheme, user, password,
852
940
         host, port, path) = transport.ConnectedTransport._split_url(proxy)
 
941
        if not host:
 
942
            raise errors.InvalidURL(proxy, 'No host component')
853
943
 
854
944
        if request.proxy_auth == {}:
855
945
            # No proxy auth parameter are available, we are handling the first
878
968
    preventively set authentication headers after the first
879
969
    successful authentication.
880
970
 
881
 
    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
882
972
    digest authentications.
883
973
 
884
974
    This provides an unified interface for all authentication handlers
909
999
      successful and the request authentication parameters have been updated.
910
1000
    """
911
1001
 
 
1002
    scheme = None
 
1003
    """The scheme as it appears in the server header (lower cased)"""
 
1004
 
912
1005
    _max_retry = 3
913
1006
    """We don't want to retry authenticating endlessly"""
914
1007
 
 
1008
    requires_username = True
 
1009
    """Whether the auth mechanism requires a username."""
 
1010
 
915
1011
    # The following attributes should be defined by daughter
916
1012
    # classes:
917
1013
    # - auth_required_header:  the header received from the server
923
1019
        # in such a cycle by default.
924
1020
        self._retry_count = None
925
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
 
926
1038
    def update_auth(self, auth, key, value):
927
1039
        """Update a value in auth marking the auth as modified if needed"""
928
1040
        old_value = auth.get(key, None)
947
1059
                # Let's be ready for next round
948
1060
                self._retry_count = None
949
1061
                return None
950
 
        server_header = headers.get(self.auth_required_header, None)
951
 
        if server_header is None:
 
1062
        server_headers = headers.getheaders(self.auth_required_header)
 
1063
        if not server_headers:
952
1064
            # The http error MUST have the associated
953
1065
            # header. This must never happen in production code.
954
1066
            raise KeyError('%s not found' % self.auth_required_header)
955
1067
 
956
1068
        auth = self.get_auth(request)
957
 
        if auth.get('user', None) is None:
958
 
            # Without a known user, we can't authenticate
959
 
            return None
960
 
 
961
1069
        auth['modified'] = False
962
 
        if self.auth_match(server_header, auth):
963
 
            # auth_match may have modified auth (by adding the
964
 
            # password or changing the realm, for example)
965
 
            if (request.get_header(self.auth_header, None) is not None
966
 
                and not auth['modified']):
967
 
                # We already tried that, give up
968
 
                return None
969
 
 
970
 
            # Housekeeping
971
 
            request.connection.cleanup_pipe()
972
 
            response = self.parent.open(request)
973
 
            if response:
974
 
                self.auth_successful(request, response)
975
 
            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
976
1118
        # We are not qualified to handle the authentication.
977
1119
        # Note: the authentication error handling will try all
978
1120
        # available handlers. If one of them authenticates
998
1140
        (digest's nonce is an example, digest's nonce_count is a
999
1141
        *counter-example*). Such parameters must be updated by
1000
1142
        using the update_auth() method.
1001
 
        
 
1143
 
1002
1144
        :param header: The authentication header sent by the server.
1003
1145
        :param auth: The auth parameters already known. They may be
1004
1146
             updated.
1028
1170
        self._retry_count = None
1029
1171
 
1030
1172
    def get_user_password(self, auth):
1031
 
        """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
        """
1032
1178
        auth_conf = config.AuthenticationConfig()
1033
 
        user = auth['user']
1034
 
        password = auth['password']
 
1179
        user = auth.get('user', None)
 
1180
        password = auth.get('password', None)
1035
1181
        realm = auth['realm']
1036
1182
 
1037
1183
        if user is None:
1038
 
            user = auth.get_user(auth['protocol'], auth['host'],
1039
 
                                 port=auth['port'], path=auth['path'],
1040
 
                                 realm=realm)
1041
 
            if user is None:
1042
 
                # Default to local user
1043
 
                user = getpass.getuser()
1044
 
 
1045
 
        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:
1046
1189
            password = auth_conf.get_password(
1047
1190
                auth['protocol'], auth['host'], user, port=auth['port'],
1048
1191
                path=auth['path'], realm=realm,
1068
1211
        prompt += ' password'
1069
1212
        return prompt
1070
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
 
1071
1232
    def http_request(self, request):
1072
1233
        """Insert an authentication header if information is available"""
1073
1234
        auth = self.get_auth(request)
1078
1239
    https_request = http_request # FIXME: Need test
1079
1240
 
1080
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
 
1081
1291
class BasicAuthHandler(AbstractAuthHandler):
1082
1292
    """A custom basic authentication handler."""
1083
1293
 
 
1294
    scheme = 'basic'
1084
1295
    handler_order = 500
1085
 
 
1086
1296
    auth_regexp = re.compile('realm="([^"]*)"', re.I)
1087
1297
 
1088
1298
    def build_auth_header(self, auth, request):
1090
1300
        auth_header = 'Basic ' + raw.encode('base64').strip()
1091
1301
        return auth_header
1092
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
 
1093
1310
    def auth_match(self, header, auth):
1094
 
        scheme, raw_auth = header.split(None, 1)
1095
 
        scheme = scheme.lower()
1096
 
        if scheme != 'basic':
 
1311
        scheme, raw_auth = self._parse_auth_header(header)
 
1312
        if scheme != self.scheme:
1097
1313
            return False
1098
1314
 
1099
 
        match = self.auth_regexp.search(raw_auth)
 
1315
        match, realm = self.extract_realm(raw_auth)
1100
1316
        if match:
1101
 
            realm = match.groups()
1102
 
            if scheme != 'basic':
1103
 
                return False
1104
 
 
1105
1317
            # Put useful info into auth
1106
1318
            self.update_auth(auth, 'scheme', scheme)
1107
1319
            self.update_auth(auth, 'realm', realm)
1108
 
            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):
1109
1322
                user, password = self.get_user_password(auth)
1110
1323
                self.update_auth(auth, 'user', user)
1111
1324
                self.update_auth(auth, 'password', password)
1139
1352
class DigestAuthHandler(AbstractAuthHandler):
1140
1353
    """A custom digest authentication handler."""
1141
1354
 
1142
 
    # 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
1143
1357
    handler_order = 490
1144
1358
 
1145
1359
    def auth_params_reusable(self, auth):
1149
1363
        return auth.get('scheme', None) == 'digest'
1150
1364
 
1151
1365
    def auth_match(self, header, auth):
1152
 
        scheme, raw_auth = header.split(None, 1)
1153
 
        scheme = scheme.lower()
1154
 
        if scheme != 'digest':
 
1366
        scheme, raw_auth = self._parse_auth_header(header)
 
1367
        if scheme != self.scheme:
1155
1368
            return False
1156
1369
 
1157
1370
        # Put the requested authentication info into a dict
1170
1383
        # Put useful info into auth
1171
1384
        self.update_auth(auth, 'scheme', scheme)
1172
1385
        self.update_auth(auth, 'realm', realm)
1173
 
        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:
1174
1387
            user, password = self.get_user_password(auth)
1175
1388
            self.update_auth(auth, 'user', user)
1176
1389
            self.update_auth(auth, 'password', password)
1252
1465
    def build_password_prompt(self, auth):
1253
1466
        return self._build_password_prompt(auth)
1254
1467
 
 
1468
    def build_username_prompt(self, auth):
 
1469
        return self._build_username_prompt(auth)
 
1470
 
1255
1471
    def http_error_401(self, req, fp, code, msg, headers):
1256
1472
        return self.auth_required(req, headers)
1257
1473
 
1283
1499
        prompt = 'Proxy ' + prompt
1284
1500
        return prompt
1285
1501
 
 
1502
    def build_username_prompt(self, auth):
 
1503
        prompt = self._build_username_prompt(auth)
 
1504
        prompt = 'Proxy ' + prompt
 
1505
        return prompt
 
1506
 
1286
1507
    def http_error_407(self, req, fp, code, msg, headers):
1287
1508
        return self.auth_required(req, headers)
1288
1509
 
1303
1524
    """Custom proxy basic authentication handler"""
1304
1525
 
1305
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
 
1306
1535
class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
1307
1536
    """Process HTTP error responses.
1308
1537
 
1359
1588
    def __init__(self,
1360
1589
                 connection=ConnectionHandler,
1361
1590
                 redirect=HTTPRedirectHandler,
1362
 
                 error=HTTPErrorProcessor,):
1363
 
        self._opener = urllib2.build_opener( \
1364
 
            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,
1365
1596
            ProxyHandler(),
1366
1597
            HTTPBasicAuthHandler(),
1367
1598
            HTTPDigestAuthHandler(),
 
1599
            HTTPNegotiateAuthHandler(),
1368
1600
            ProxyBasicAuthHandler(),
1369
1601
            ProxyDigestAuthHandler(),
 
1602
            ProxyNegotiateAuthHandler(),
1370
1603
            HTTPHandler,
1371
1604
            HTTPSHandler,
1372
1605
            HTTPDefaultErrorHandler,