~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http/_urllib2_wrappers.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:
146
146
    def __init__(self, method, url, data=None, headers={},
147
147
                 origin_req_host=None, unverifiable=False,
148
148
                 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
 
        self.auth = None # Until the first 401
153
149
        urllib2.Request.__init__(self, url, data, headers,
154
150
                                 origin_req_host, unverifiable)
155
151
        self.method = method
159
155
        self.redirected_to = None
160
156
        # Unless told otherwise, redirections are not followed
161
157
        self.follow_redirections = False
162
 
 
163
 
    def extract_auth(self, url):
164
 
        """Extracts authentification information from url.
165
 
 
166
 
        Get user and password from url of the form: http://user:pass@host/path
167
 
        """
168
 
        scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
169
 
 
170
 
        if '@' in netloc:
171
 
            auth, netloc = netloc.split('@', 1)
172
 
            if ':' in auth:
173
 
                user, password = auth.split(':', 1)
174
 
            else:
175
 
                user, password = auth, None
176
 
            user = urllib.unquote(user)
177
 
            if password is not None:
178
 
                password = urllib.unquote(password)
179
 
        else:
180
 
            user = None
181
 
            password = None
182
 
 
183
 
        url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
184
 
 
185
 
        return url, user, password
 
158
        self.set_auth(None, None, None) # Until the first 401
 
159
 
 
160
    def set_auth(self, auth_scheme, user, password=None):
 
161
        self.auth = auth_scheme
 
162
        self.user = user
 
163
        self.password = password
186
164
 
187
165
    def get_method(self):
188
166
        return self.method
197
175
    def __init__(self):
198
176
        urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
199
177
 
200
 
    def add_password(self, realm, uri, user, passwd):
201
 
        # uri could be a single URI or a sequence
202
 
        if isinstance(uri, basestring):
203
 
            uri = [uri]
204
 
        if not realm in self.passwd:
205
 
            self.passwd[realm] = {}
206
 
        for default_port in True, False:
207
 
            reduced_uri = tuple(
208
 
                [self.reduce_uri(u, default_port) for u in uri])
209
 
            self.passwd[realm][reduced_uri] = (user, passwd)
210
 
 
211
 
    def find_user_password(self, realm, authuri):
212
 
        domains = self.passwd.get(realm, {})
213
 
        for default_port in True, False:
214
 
            reduced_authuri = self.reduce_uri(authuri, default_port)
215
 
            for uris, authinfo in domains.iteritems():
216
 
                for uri in uris:
217
 
                    if self.is_suburi(uri, reduced_authuri):
218
 
                        return authinfo
219
 
        if realm is not None:
220
 
            return self.find_user_password(None, authuri)
221
 
        return None, None
222
 
 
223
 
    def reduce_uri(self, uri, default_port=True):
224
 
        """Accept authority or URI and extract only the authority and path."""
225
 
        # note HTTP URLs do not have a userinfo component
226
 
        parts = urlparse.urlsplit(uri)
227
 
        if parts[1]:
228
 
            # URI
229
 
            scheme = parts[0]
230
 
            authority = parts[1]
231
 
            path = parts[2] or '/'
232
 
        else:
233
 
            # host or host:port
234
 
            scheme = None
235
 
            authority = uri
236
 
            path = '/'
237
 
        host, port = urllib.splitport(authority)
238
 
        if default_port and port is None and scheme is not None:
239
 
            dport = {"http": 80,
240
 
                     "https": 443,
241
 
                     }.get(scheme)
242
 
            if dport is not None:
243
 
                authority = "%s:%d" % (host, dport)
244
 
        return authority, path
245
 
 
246
178
 
247
179
class ConnectionHandler(urllib2.BaseHandler):
248
180
    """Provides connection-sharing by pre-processing requests.
788
720
 
789
721
    https_request = http_request # FIXME: Need test
790
722
 
 
723
    def http_error_401(self, req, fp, code, msg, headers):
 
724
        """Trap the 401 to gather the auth type."""
 
725
        response = urllib2.HTTPBasicAuthHandler.http_error_401(self, req, fp,
 
726
                                                               code, msg,
 
727
                                                               headers)
 
728
        if response is not None:
 
729
            req.auth = 'basic'
 
730
 
 
731
        return response
 
732
 
791
733
 
792
734
class HTTPErrorProcessor(urllib2.HTTPErrorProcessor):
793
735
    """Process HTTP error responses.