~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Vincent Ladeuil
  • Date: 2007-04-13 12:17:40 UTC
  • mto: (2420.1.1 bzr.http.auth)
  • mto: This revision was merged to the branch mainline in revision 2463.
  • Revision ID: v.ladeuil+lp@free.fr-20070413121740-mnwzf1656e31aenj
Catch first succesful authentification to avoid further 401
roudtrips in hhtp urllib implementation.

* bzrlib/transport/http/_urllib2_wrappers.py:
(Request.__init__): Initialize auth parameters.
(Request.extract_auth): Moved to
HttpTransport_urllib._extract_auth.
(Request.set_auth): New method.
(PasswordManager): Now that the transport handles the auth
parameters, we can use transport.base as the auth uri and work
around the python-2.4 bug.
(HTTPBasicAuthHandler.http_error_401): Capture the auth scheme
when the authentication succeeds.

* bzrlib/transport/http/_urllib.py:
(HttpTransport_urllib.__init__): Extract authentication at
construction time so that we don't have to do it at request build
time. urllib2 will be happier without it.
(HttpTransport_urllib._extract_auth): Moved from
_urllib2_wrappers.Request.extract_auth.
(HttpTransport_urllib._ask_password): Made private and do not
require a 'request' parameter anymore.
(HttpTransport_urllib._perform): The transport is now responsible
for handling the auth parameters and provide them to the
requests. And from there we can avoid the 401 roundtrips
yeaaaaah! (Except the first one of course to determine the auth
scheme).

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
from cStringIO import StringIO
 
18
import urllib
 
19
import urlparse
18
20
 
19
21
from bzrlib import (
20
22
    ui,
45
47
 
46
48
    def __init__(self, base, from_transport=None):
47
49
        """Set the base path where files will be stored."""
48
 
        super(HttpTransport_urllib, self).__init__(base, from_transport)
49
50
        if from_transport is not None:
 
51
            super(HttpTransport_urllib, self).__init__(base, from_transport)
50
52
            self._connection = from_transport._connection
 
53
            self._auth = from_transport._auth
51
54
            self._user = from_transport._user
52
55
            self._password = from_transport._password
53
56
            self._opener = from_transport._opener
54
57
        else:
 
58
            # urllib2 will be confused if it find
 
59
            # authentification infos in the urls. So we handle
 
60
            # them separatly. Note that we don't need to do that
 
61
            # when cloning (as above) since the cloned base is
 
62
            # already clean.
 
63
            clean_base, user, password = self._extract_auth(base)
 
64
            super(HttpTransport_urllib, self).__init__(clean_base,
 
65
                                                       from_transport)
55
66
            self._connection = None
56
 
            self._user = None
57
 
            self._password = None
 
67
            self._auth = None # We have to wait the 401 to know
 
68
            self._user = user
 
69
            self._password = password
58
70
            self._opener = self._opener_class()
 
71
            if password is not None: # '' is a valid password
 
72
                # Make the (user, password) available to urllib2
 
73
                pm = self._opener.password_manager
 
74
                pm.add_password(None, self.base, self._user, self._password)
59
75
 
60
 
    def ask_password(self, request):
61
 
        """Ask for a password if none is already provided in the request"""
 
76
    def _ask_password(self):
 
77
        """Ask for a password if none is already available"""
62
78
        # TODO: jam 20060915 There should be a test that asserts we ask 
63
79
        #       for a password at the right time.
64
 
        host = request.get_host()
65
 
        password_manager = self._opener.password_manager
66
 
        if request.password is None:
 
80
        if self._password is None:
67
81
            # We can't predict realm, let's try None, we'll get a
68
82
            # 401 if we are wrong anyway
69
83
            realm = None
70
84
            # Query the password manager first
71
 
            user, password = password_manager.find_user_password(None, host)
72
 
            if user == request.user and password is not None:
73
 
                request.password = password
 
85
            authuri = self.base
 
86
            pm = self._opener.password_manager
 
87
            user, password = pm.find_user_password(None, authuri)
 
88
            if user == self._user and password is not None:
 
89
                self._password = password
74
90
            else:
75
91
                # Ask the user if we MUST
76
92
                http_pass = 'HTTP %(user)s@%(host)s password'
77
 
                request.password = ui.ui_factory.get_password(prompt=http_pass,
78
 
                                                              user=request.user,
79
 
                                                              host=host)
80
 
        password_manager.add_password(None, host,
81
 
                                      request.user, request.password)
 
93
                self._password = ui.ui_factory.get_password(prompt=http_pass,
 
94
                                                            user=self._user,
 
95
                                                            host=self._host)
 
96
                pm.add_password(None, authuri, self._user, self._password)
 
97
 
 
98
    def _extract_auth(self, url):
 
99
        """Extracts authentification information from url.
 
100
 
 
101
        Get user and password from url of the form: http://user:pass@host/path
 
102
        :returns: (clean_url, user, password)
 
103
        """
 
104
        scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
 
105
 
 
106
        if '@' in netloc:
 
107
            auth, netloc = netloc.split('@', 1)
 
108
            if ':' in auth:
 
109
                user, password = auth.split(':', 1)
 
110
            else:
 
111
                user, password = auth, None
 
112
            user = urllib.unquote(user)
 
113
            if password is not None:
 
114
                password = urllib.unquote(password)
 
115
        else:
 
116
            user = None
 
117
            password = None
 
118
 
 
119
        url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
 
120
 
 
121
        return url, user, password
82
122
 
83
123
    def _perform(self, request):
84
124
        """Send the request to the server and handles common errors.
88
128
        if self._connection is not None:
89
129
            # Give back shared info
90
130
            request.connection = self._connection
91
 
            if self._user is not None:
92
 
                request.user = self._user
93
 
                request.password = self._password
94
 
        elif request.user is not None:
 
131
        elif self._user is not None:
95
132
            # We will issue our first request, time to ask for a
96
133
            # password if needed
97
 
            self.ask_password(request)
 
134
            self._ask_password()
 
135
        # Ensure authentification info is provided
 
136
        request.set_auth(self._auth, self._user, self._password)
98
137
 
99
138
        mutter('%s: [%s]' % (request.method, request.get_full_url()))
100
139
        if self._debuglevel > 0:
106
145
            # Acquire connection when the first request is able
107
146
            # to connect to the server
108
147
            self._connection = request.connection
 
148
            # And get auth parameters too
 
149
            self._auth = request.auth
109
150
            self._user = request.user
110
151
            self._password = request.password
111
152