17
17
"""Implementaion of urllib2 tailored to bzr needs
19
This file re-implements the urllib2 class hierarchy with custom classes.
19
This file complements the urllib2 class hierarchy with custom classes.
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.
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.
34
We also create a Request hierarchy, to make it clear what type of
35
request is being made.
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
162
def extract_auth(self, url):
163
"""Extracts authentification information from url.
165
Get user and password from url of the form: http://user:pass@host/path
167
scheme, netloc, path, query, fragment = urlparse.urlsplit(url)
170
auth, netloc = netloc.split('@', 1)
172
user, password = auth.split(':', 1)
174
user, password = auth, None
175
user = urllib.unquote(user)
176
if password is not None:
177
password = urllib.unquote(password)
182
url = urlparse.urlunsplit((scheme, netloc, path, query, fragment))
184
return url, user, password
159
self.set_auth(None, None, None) # Until the first 401
161
def set_auth(self, auth_scheme, user, password=None):
162
self.auth_scheme = auth_scheme
164
self.password = password
186
166
def get_method(self):
187
167
return self.method
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
187
http[s] requests in AbstractHTTPHandler.
210
190
handler_order = 1000 # after all pre-processings
212
def get_key(self, connection):
213
"""Returns the key for the connection in the cache"""
214
return '%s:%d' % (connection.host, connection.port)
216
192
def create_connection(self, request, http_connection_class):
217
193
host = request.get_host()
720
695
class HTTPBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
721
"""Custom basic authentification handler.
696
"""Custom basic authentication handler.
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.
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:
702
def get_auth(self, user, password):
703
raw = '%s:%s' % (user, password)
704
auth = 'Basic ' + raw.encode('base64').strip()
707
def set_auth(self, request):
708
"""Add the authentication header if needed.
710
All required informations should be part of the request.
712
if request.password is not None:
713
request.add_header(self.auth_header,
714
self.get_auth(request.user, request.password))
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)
722
https_request = http_request # FIXME: Need test
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,
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'
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.
740
handler_order = 1000 # after all other processing
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)
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'
792
798
self._opener = urllib2.build_opener( \
793
799
connection, redirect, error,
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,