/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-03-13 07:54:48 UTC
  • mfrom: (4144 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4189.
  • Revision ID: mbp@sourcefrog.net-20090313075448-jlz1t7baz7gzipqn
merge trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
47
47
# ensure that.
48
48
 
49
49
import httplib
 
50
try:
 
51
    import kerberos
 
52
except ImportError:
 
53
    have_kerberos = False
 
54
else:
 
55
    have_kerberos = True
50
56
import socket
51
57
import urllib
52
58
import urllib2
67
73
    )
68
74
 
69
75
 
70
 
class _BufferedMakefileSocket(object):
71
 
 
72
 
    def __init__(self, sock):
 
76
class _ReportingFileSocket(object):
 
77
 
 
78
    def __init__(self, filesock, report_activity=None):
 
79
        self.filesock = filesock
 
80
        self._report_activity = report_activity
 
81
 
 
82
 
 
83
    def read(self, size=1):
 
84
        s = self.filesock.read(size)
 
85
        self._report_activity(len(s), 'read')
 
86
        return s
 
87
 
 
88
    def readline(self):
 
89
        # This should be readline(self, size=-1), but httplib in python 2.4 and
 
90
        #  2.5 defines a SSLFile wrapper whose readline method lacks the size
 
91
        #  parameter.  So until we drop support for 2.4 and 2.5 and since we
 
92
        #  don't *need* the size parameter we'll stay with readline(self)
 
93
        #  --  vila 20090209
 
94
        s = self.filesock.readline()
 
95
        self._report_activity(len(s), 'read')
 
96
        return s
 
97
 
 
98
    def __getattr__(self, name):
 
99
        return getattr(self.filesock, name)
 
100
 
 
101
 
 
102
class _ReportingSocket(object):
 
103
 
 
104
    def __init__(self, sock, report_activity=None):
73
105
        self.sock = sock
 
106
        self._report_activity = report_activity
 
107
 
 
108
    def sendall(self, s, *args):
 
109
        self.sock.sendall(s, *args)
 
110
        self._report_activity(len(s), 'write')
 
111
 
 
112
    def recv(self, *args):
 
113
        s = self.sock.recv(*args)
 
114
        self._report_activity(len(s), 'read')
 
115
        return s
74
116
 
75
117
    def makefile(self, mode='r', bufsize=-1):
76
 
        return self.sock.makefile(mode, 65536)
 
118
        # httplib creates a fileobject that doesn't do buffering, which
 
119
        # makes fp.readline() very expensive because it only reads one byte
 
120
        # at a time.  So we wrap the socket in an object that forces
 
121
        # sock.makefile to make a buffered file.
 
122
        fsock = self.sock.makefile(mode, 65536)
 
123
        # And wrap that into a reporting kind of fileobject
 
124
        return _ReportingFileSocket(fsock, self._report_activity)
77
125
 
78
126
    def __getattr__(self, name):
79
127
        return getattr(self.sock, name)
96
144
    # 8k chunks should be fine.
97
145
    _discarded_buf_size = 8192
98
146
 
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
147
    def begin(self):
108
148
        """Begin to read the response from the server.
109
149
 
178
218
    # we want to warn. But not below a given thresold.
179
219
    _range_warning_thresold = 1024 * 1024
180
220
 
181
 
    def __init__(self):
 
221
    def __init__(self,
 
222
                 report_activity=None):
182
223
        self._response = None
 
224
        self._report_activity = report_activity
183
225
        self._ranges_received_whole_file = None
184
226
 
185
227
    def _mutter_connect(self):
211
253
        # Preserve our preciousss
212
254
        sock = self.sock
213
255
        self.sock = None
214
 
        # Let httplib.HTTPConnection do its housekeeping 
 
256
        # Let httplib.HTTPConnection do its housekeeping
215
257
        self.close()
216
258
        # Restore our preciousss
217
259
        self.sock = sock
218
260
 
 
261
    def _wrap_socket_for_reporting(self, sock):
 
262
        """Wrap the socket before anybody use it."""
 
263
        self.sock = _ReportingSocket(sock, self._report_activity)
 
264
 
219
265
 
220
266
class HTTPConnection(AbstractHTTPConnection, httplib.HTTPConnection):
221
267
 
222
268
    # XXX: Needs refactoring at the caller level.
223
 
    def __init__(self, host, port=None, proxied_host=None):
224
 
        AbstractHTTPConnection.__init__(self)
 
269
    def __init__(self, host, port=None, proxied_host=None,
 
270
                 report_activity=None):
 
271
        AbstractHTTPConnection.__init__(self, report_activity=report_activity)
225
272
        # Use strict=True since we don't support HTTP/0.9
226
273
        httplib.HTTPConnection.__init__(self, host, port, strict=True)
227
274
        self.proxied_host = proxied_host
230
277
        if 'http' in debug.debug_flags:
231
278
            self._mutter_connect()
232
279
        httplib.HTTPConnection.connect(self)
 
280
        self._wrap_socket_for_reporting(self.sock)
233
281
 
234
282
 
235
283
# Build the appropriate socket wrapper for ssl
248
296
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
249
297
 
250
298
    def __init__(self, host, port=None, key_file=None, cert_file=None,
251
 
                 proxied_host=None):
252
 
        AbstractHTTPConnection.__init__(self)
 
299
                 proxied_host=None,
 
300
                 report_activity=None):
 
301
        AbstractHTTPConnection.__init__(self, report_activity=report_activity)
253
302
        # Use strict=True since we don't support HTTP/0.9
254
303
        httplib.HTTPSConnection.__init__(self, host, port,
255
304
                                         key_file, cert_file, strict=True)
259
308
        if 'http' in debug.debug_flags:
260
309
            self._mutter_connect()
261
310
        httplib.HTTPConnection.connect(self)
 
311
        self._wrap_socket_for_reporting(self.sock)
262
312
        if self.proxied_host is None:
263
313
            self.connect_to_origin()
264
314
 
265
315
    def connect_to_origin(self):
266
 
        self.sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
 
316
        ssl_sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
 
317
        # Wrap the ssl socket before anybody use it
 
318
        self._wrap_socket_for_reporting(ssl_sock)
267
319
 
268
320
 
269
321
class Request(urllib2.Request):
315
367
 
316
368
    def __init__(self, request):
317
369
        """Constructor
318
 
        
 
370
 
319
371
        :param request: the first request sent to the proxied host, already
320
372
            processed by the opener (i.e. proxied_host is already set).
321
373
        """
355
407
 
356
408
    handler_order = 1000 # after all pre-processings
357
409
 
 
410
    def __init__(self, report_activity=None):
 
411
        self._report_activity = report_activity
 
412
 
358
413
    def create_connection(self, request, http_connection_class):
359
414
        host = request.get_host()
360
415
        if not host:
366
421
        # request is made)
367
422
        try:
368
423
            connection = http_connection_class(
369
 
                host, proxied_host=request.proxied_host)
 
424
                host, proxied_host=request.proxied_host,
 
425
                report_activity=self._report_activity)
370
426
        except httplib.InvalidURL, exception:
371
427
            # There is only one occurrence of InvalidURL in httplib
372
428
            raise errors.InvalidURL(request.get_full_url(),
638
694
                        connect.proxied_host, self.host))
639
695
            # Housekeeping
640
696
            connection.cleanup_pipe()
641
 
            # Establish the connection encryption 
 
697
            # Establish the connection encryption
642
698
            connection.connect_to_origin()
643
699
            # Propagate the connection to the original request
644
700
            request.connection = connection
889
945
    preventively set authentication headers after the first
890
946
    successful authentication.
891
947
 
892
 
    This can be used for http and proxy, as well as for basic and
 
948
    This can be used for http and proxy, as well as for basic, negotiate and
893
949
    digest authentications.
894
950
 
895
951
    This provides an unified interface for all authentication handlers
923
979
    _max_retry = 3
924
980
    """We don't want to retry authenticating endlessly"""
925
981
 
 
982
    requires_username = True
 
983
    """Whether the auth mechanism requires a username."""
 
984
 
926
985
    # The following attributes should be defined by daughter
927
986
    # classes:
928
987
    # - auth_required_header:  the header received from the server
934
993
        # in such a cycle by default.
935
994
        self._retry_count = None
936
995
 
 
996
    def _parse_auth_header(self, server_header):
 
997
        """Parse the authentication header.
 
998
 
 
999
        :param server_header: The value of the header sent by the server
 
1000
            describing the authenticaion request.
 
1001
 
 
1002
        :return: A tuple (scheme, remainder) scheme being the first word in the
 
1003
            given header (lower cased), remainder may be None.
 
1004
        """
 
1005
        try:
 
1006
            scheme, remainder = server_header.split(None, 1)
 
1007
        except ValueError:
 
1008
            scheme = server_header
 
1009
            remainder = None
 
1010
        return (scheme.lower(), remainder)
 
1011
 
937
1012
    def update_auth(self, auth, key, value):
938
1013
        """Update a value in auth marking the auth as modified if needed"""
939
1014
        old_value = auth.get(key, None)
974
1049
                # We already tried that, give up
975
1050
                return None
976
1051
 
977
 
            if auth.get('user', None) is None:
 
1052
            if self.requires_username and auth.get('user', None) is None:
978
1053
                # Without a known user, we can't authenticate
979
1054
                return None
980
1055
 
1009
1084
        (digest's nonce is an example, digest's nonce_count is a
1010
1085
        *counter-example*). Such parameters must be updated by
1011
1086
        using the update_auth() method.
1012
 
        
 
1087
 
1013
1088
        :param header: The authentication header sent by the server.
1014
1089
        :param auth: The auth parameters already known. They may be
1015
1090
             updated.
1089
1164
    https_request = http_request # FIXME: Need test
1090
1165
 
1091
1166
 
 
1167
class NegotiateAuthHandler(AbstractAuthHandler):
 
1168
    """A authentication handler that handles WWW-Authenticate: Negotiate.
 
1169
 
 
1170
    At the moment this handler supports just Kerberos. In the future,
 
1171
    NTLM support may also be added.
 
1172
    """
 
1173
 
 
1174
    handler_order = 480
 
1175
 
 
1176
    requires_username = False
 
1177
 
 
1178
    def auth_match(self, header, auth):
 
1179
        scheme, raw_auth = self._parse_auth_header(header)
 
1180
        if scheme != 'negotiate':
 
1181
            return False
 
1182
        self.update_auth(auth, 'scheme', scheme)
 
1183
        resp = self._auth_match_kerberos(auth)
 
1184
        if resp is None:
 
1185
            return False
 
1186
        # Optionally should try to authenticate using NTLM here
 
1187
        self.update_auth(auth, 'negotiate_response', resp)
 
1188
        return True
 
1189
 
 
1190
    def _auth_match_kerberos(self, auth):
 
1191
        """Try to create a GSSAPI response for authenticating against a host."""
 
1192
        if not have_kerberos:
 
1193
            return None
 
1194
        ret, vc = kerberos.authGSSClientInit("HTTP@%(host)s" % auth)
 
1195
        if ret < 1:
 
1196
            trace.warning('Unable to create GSSAPI context for %s: %d',
 
1197
                auth['host'], ret)
 
1198
            return None
 
1199
        ret = kerberos.authGSSClientStep(vc, "")
 
1200
        if ret < 0:
 
1201
            trace.mutter('authGSSClientStep failed: %d', ret)
 
1202
            return None
 
1203
        return kerberos.authGSSClientResponse(vc)
 
1204
 
 
1205
    def build_auth_header(self, auth, request):
 
1206
        return "Negotiate %s" % auth['negotiate_response']
 
1207
 
 
1208
    def auth_params_reusable(self, auth):
 
1209
        # If the auth scheme is known, it means a previous
 
1210
        # authentication was successful, all information is
 
1211
        # available, no further checks are needed.
 
1212
        return (auth.get('scheme', None) == 'negotiate' and
 
1213
                auth.get('negotiate_response', None) is not None)
 
1214
 
 
1215
 
1092
1216
class BasicAuthHandler(AbstractAuthHandler):
1093
1217
    """A custom basic authentication handler."""
1094
1218
 
1102
1226
        return auth_header
1103
1227
 
1104
1228
    def auth_match(self, header, auth):
1105
 
        scheme, raw_auth = header.split(None, 1)
1106
 
        scheme = scheme.lower()
 
1229
        scheme, raw_auth = self._parse_auth_header(header)
1107
1230
        if scheme != 'basic':
1108
1231
            return False
1109
1232
 
1150
1273
class DigestAuthHandler(AbstractAuthHandler):
1151
1274
    """A custom digest authentication handler."""
1152
1275
 
1153
 
    # Before basic as digest is a bit more secure
 
1276
    # Before basic as digest is a bit more secure and should be preferred
1154
1277
    handler_order = 490
1155
1278
 
1156
1279
    def auth_params_reusable(self, auth):
1160
1283
        return auth.get('scheme', None) == 'digest'
1161
1284
 
1162
1285
    def auth_match(self, header, auth):
1163
 
        scheme, raw_auth = header.split(None, 1)
1164
 
        scheme = scheme.lower()
 
1286
        scheme, raw_auth = self._parse_auth_header(header)
1165
1287
        if scheme != 'digest':
1166
1288
            return False
1167
1289
 
1314
1436
    """Custom proxy basic authentication handler"""
1315
1437
 
1316
1438
 
 
1439
class HTTPNegotiateAuthHandler(NegotiateAuthHandler, HTTPAuthHandler):
 
1440
    """Custom http negotiate authentication handler"""
 
1441
 
 
1442
 
 
1443
class ProxyNegotiateAuthHandler(NegotiateAuthHandler, ProxyAuthHandler):
 
1444
    """Custom proxy negotiate authentication handler"""
 
1445
 
 
1446
 
1317
1447
class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
1318
1448
    """Process HTTP error responses.
1319
1449
 
1370
1500
    def __init__(self,
1371
1501
                 connection=ConnectionHandler,
1372
1502
                 redirect=HTTPRedirectHandler,
1373
 
                 error=HTTPErrorProcessor,):
1374
 
        self._opener = urllib2.build_opener( \
1375
 
            connection, redirect, error,
 
1503
                 error=HTTPErrorProcessor,
 
1504
                 report_activity=None):
 
1505
        self._opener = urllib2.build_opener(
 
1506
            connection(report_activity=report_activity),
 
1507
            redirect, error,
1376
1508
            ProxyHandler(),
1377
1509
            HTTPBasicAuthHandler(),
1378
1510
            HTTPDigestAuthHandler(),
 
1511
            HTTPNegotiateAuthHandler(),
1379
1512
            ProxyBasicAuthHandler(),
1380
1513
            ProxyDigestAuthHandler(),
 
1514
            ProxyNegotiateAuthHandler(),
1381
1515
            HTTPHandler,
1382
1516
            HTTPSHandler,
1383
1517
            HTTPDefaultErrorHandler,