~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

mergeĀ basicĀ auth

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Implementaion of urllib2 tailored to bzr needs
18
18
 
19
 
This file re-implements the urllib2 class hierarchy with custom classes.
 
19
This file complements the urllib2 class hierarchy with custom classes.
20
20
 
21
21
For instance, we create a new HTTPConnection and HTTPSConnection that inherit
22
22
from the original urllib2.HTTP(s)Connection objects, but also have a new base
28
28
We have a custom Response class, which lets us maintain a keep-alive
29
29
connection even for requests that urllib2 doesn't expect to contain body data.
30
30
 
31
 
And a custom Request class that lets us track redirections, and send
32
 
authentication data without requiring an extra round trip to get rejected by
33
 
the server. We also create a Request hierarchy, to make it clear what type
34
 
of request is being made.
 
31
And a custom Request class that lets us track redirections, and
 
32
handle authentication schemes.
 
33
 
 
34
We also create a Request hierarchy, to make it clear what type of
 
35
request is being made.
35
36
"""
36
37
 
37
38
DEBUG = 0
146
147
    def __init__(self, method, url, data=None, headers={},
147
148
                 origin_req_host=None, unverifiable=False,
148
149
                 connection=None, parent=None,):
149
 
        # urllib2.Request will be confused if we don't extract
150
 
        # authentification info before building the request
151
 
        url, self.user, self.password = self.extract_auth(url)
152
150
        urllib2.Request.__init__(self, url, data, headers,
153
151
                                 origin_req_host, unverifiable)
154
152
        self.method = method
158
156
        self.redirected_to = None
159
157
        # Unless told otherwise, redirections are not followed
160
158
        self.follow_redirections = False
161
 
 
162
 
    def extract_auth(self, url):
163
 
        """Extracts authentification information from url.
164
 
 
165
 
        Get user and password from url of the form: http://user:pass@host/path
166
 
        """
167
 
        scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
168
 
 
169
 
        if '@' in netloc:
170
 
            auth, netloc = netloc.split('@', 1)
171
 
            if ':' in auth:
172
 
                user, password = auth.split(':', 1)
173
 
            else:
174
 
                user, password = auth, None
175
 
            user = urllib.unquote(user)
176
 
            if password is not None:
177
 
                password = urllib.unquote(password)
178
 
        else:
179
 
            user = None
180
 
            password = None
181
 
 
182
 
        url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
183
 
 
184
 
        return url, user, password
 
159
        self.set_auth(None, None, None) # Until the first 401
 
160
 
 
161
    def set_auth(self, auth_scheme, user, password=None):
 
162
        self.auth_scheme = auth_scheme
 
163
        self.user = user
 
164
        self.password = password
185
165
 
186
166
    def get_method(self):
187
167
        return self.method
188
168
 
189
169
 
190
 
# The urlib2.xxxAuthHandler handle the authentification of the
 
170
# The urlib2.xxxAuthHandler handle the authentication of the
191
171
# requests, to do that, they need an urllib2 PasswordManager *at
192
172
# build time*. We also need one to reuse the passwords already
193
173
# typed by the user.
204
184
    internally used. But we need it in order to achieve
205
185
    connection sharing. So, we add it to the request just before
206
186
    it is processed, and then we override the do_open method for
207
 
    http[s] requests.
 
187
    http[s] requests in AbstractHTTPHandler.
208
188
    """
209
189
 
210
190
    handler_order = 1000 # after all pre-processings
211
191
 
212
 
    def get_key(self, connection):
213
 
        """Returns the key for the connection in the cache"""
214
 
        return '%s:%d' % (connection.host, connection.port)
215
 
 
216
192
    def create_connection(self, request, http_connection_class):
217
193
        host = request.get_host()
218
194
        if not host:
282
258
                        # urllib2 using capitalize() for headers
283
259
                        # instead of title(sp?).
284
260
                        'User-agent': 'bzr/%s (urllib)' % bzrlib_version,
285
 
                        # FIXME: pycurl also set the following, understand why
286
261
                        'Accept': '*/*',
287
262
                        }
288
263
 
718
693
 
719
694
 
720
695
class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
721
 
    """Custom basic authentification handler.
 
696
    """Custom basic authentication handler.
722
697
 
723
 
    Send the authentification preventively to avoid the the
724
 
    roundtrip associated with the 401 error.
 
698
    Send the authentication preventively to avoid the roundtrip
 
699
    associated with the 401 error.
725
700
    """
726
701
 
727
 
#    def http_request(self, request):
728
 
#        """Insert an authentification header if information is available"""
729
 
#        if request.auth == 'basic' and request.password is not None:
730
 
#            
731
 
#        return request
 
702
    def get_auth(self, user, password):
 
703
        raw = '%s:%s' % (user, password)
 
704
        auth = 'Basic ' + raw.encode('base64').strip()
 
705
        return auth
 
706
 
 
707
    def set_auth(self, request):
 
708
        """Add the authentication header if needed.
 
709
 
 
710
        All required informations should be part of the request.
 
711
        """
 
712
        if request.password is not None:
 
713
            request.add_header(self.auth_header,
 
714
                               self.get_auth(request.user, request.password))
 
715
 
 
716
    def http_request(self, request):
 
717
        """Insert an authentication header if information is available"""
 
718
        if request.auth_scheme == 'basic' and request.password is not None:
 
719
            self.set_auth(request)
 
720
        return request
 
721
 
 
722
    https_request = http_request # FIXME: Need test
 
723
 
 
724
    def http_error_401(self, req, fp, code, msg, headers):
 
725
        """Trap the 401 to gather the auth type."""
 
726
        response = urllib2.HTTPBasicAuthHandler.http_error_401(self, req, fp,
 
727
                                                               code, msg,
 
728
                                                               headers)
 
729
        if response is not None:
 
730
            # We capture the auth_scheme to be able to send the
 
731
            # authentication header with the next requests
 
732
            # without waiting for a 401 error.
 
733
            # The urllib2.HTTPBasicAuthHandler will return a
 
734
            # response *only* if the basic authentication
 
735
            # succeeds. If another scheme is used or the
 
736
            # authentication fails, the response will be None.
 
737
            req.auth_scheme = 'basic'
 
738
 
 
739
        return response
732
740
 
733
741
 
734
742
class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
737
745
    We don't really process the errors, quite the contrary
738
746
    instead, we leave our Transport handle them.
739
747
    """
740
 
    handler_order = 1000  # after all other processing
741
748
 
742
749
    def http_response(self, request, response):
743
750
        code, msg, hdrs = response.code, response.msg, response.info()
770
777
            # of a better magic value.
771
778
            raise errors.InvalidRange(req.get_full_url(),0)
772
779
        else:
773
 
            # TODO: A test is needed to exercise that code path
774
780
            raise errors.InvalidHttpResponse(req.get_full_url(),
775
781
                                             'Unable to handle http code %d: %s'
776
782
                                             % (code, msg))
792
798
        self._opener = urllib2.build_opener( \
793
799
            connection, redirect, error,
794
800
            ProxyHandler,
795
 
            urllib2.HTTPBasicAuthHandler(self.password_manager),
 
801
            HTTPBasicAuthHandler(self.password_manager),
796
802
            #urllib2.HTTPDigestAuthHandler(self.password_manager),
797
803
            #urllib2.ProxyBasicAuthHandler,
798
804
            #urllib2.ProxyDigestAuthHandler,
807
813
            # handler is used, when and for what.
808
814
            import pprint
809
815
            pprint.pprint(self._opener.__dict__)
810