108
108
if self.debuglevel > 0:
109
109
print "Consumed body: [%s]" % body
111
elif self.status == 200:
112
# Whatever the request is, it went ok, so we surely don't want to
113
# close the connection. Some cases are not correctly detected by
114
# httplib.HTTPConnection.getresponse (called by
115
# httplib.HTTPResponse.begin). The CONNECT response for the https
116
# through proxy case is one.
117
self.will_close = False
113
120
# Not inheriting from 'object' because httplib.HTTPConnection doesn't.
125
132
# Preserve our preciousss
135
# Let httplib.HTTPConnection do its housekeeping
137
# Restore our preciousss
132
141
class HTTPConnection(AbstractHTTPConnection, httplib.HTTPConnection):
143
# XXX: Needs refactoring at the caller level.
144
def __init__(self, host, port=None, strict=None, proxied_host=None):
145
httplib.HTTPConnection.__init__(self, host, port, strict)
146
self.proxied_host = proxied_host
136
149
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
151
def __init__(self, host, port=None, key_file=None, cert_file=None,
152
strict=None, proxied_host=None):
153
httplib.HTTPSConnection.__init__(self, host, port,
154
key_file, cert_file, strict)
155
self.proxied_host = proxied_host
158
httplib.HTTPConnection.connect(self)
159
if self.proxied_host is None:
160
self.connect_to_origin()
162
def connect_to_origin(self):
163
ssl = socket.ssl(self.sock, self.key_file, self.cert_file)
164
self.sock = httplib.FakeSocket(self.sock, ssl)
140
167
class Request(urllib2.Request):
154
181
def __init__(self, method, url, data=None, headers={},
155
182
origin_req_host=None, unverifiable=False,
156
connection=None, parent=None,):
183
connection=None, parent=None,
184
accepted_errors=None):
157
185
urllib2.Request.__init__(self, url, data, headers,
158
186
origin_req_host, unverifiable)
159
187
self.method = method
160
188
self.connection = connection
189
self.accepted_errors = accepted_errors
161
190
# To handle redirections
162
191
self.parent = parent
163
192
self.redirected_to = None
169
198
# Some authentication schemes may add more entries.
171
200
self.proxy_auth = {}
201
self.proxied_host = None
173
203
def get_method(self):
174
204
return self.method
206
def set_proxy(self, proxy, type):
207
"""Set the proxy and remember the proxied host."""
208
self.proxied_host = self.get_host()
209
urllib2.Request.set_proxy(self, proxy, type)
212
class _ConnectRequest(Request):
214
def __init__(self, request):
217
:param request: the first request sent to the proxied host, already
218
processed by the opener (i.e. proxied_host is already set).
220
# We give a fake url and redefine get_selector or urllib2 will be
222
Request.__init__(self, 'CONNECT', request.get_full_url(),
223
connection=request.connection)
224
assert request.proxied_host is not None
225
self.proxied_host = request.proxied_host
227
def get_selector(self):
228
return self.proxied_host
230
def set_proxy(self, proxy, type):
231
"""Set the proxy without remembering the proxied host.
233
We already know the proxied host by definition, the CONNECT request
234
occurs only when the connection goes through a proxy. The usual
235
processing (masquerade the request so that the connection is done to
236
the proxy while the request is targeted at another host) does not apply
237
here. In fact, the connection is already established with proxy and we
238
just want to enable the SSL tunneling.
240
urllib2.Request.set_proxy(self, proxy, type)
177
243
def extract_credentials(url):
178
244
"""Extracts credentials information from url.
213
279
return '%s://%s' % (scheme, host)
216
# The urlib2.xxxAuthHandler handle the authentication of the
217
# requests, to do that, they need an urllib2 PasswordManager *at
218
# build time*. We also need one to reuse the passwords entered by
282
# The AuthHandler classes handle the authentication of the requests, to do
283
# that, they need a PasswordManager *at build time*. We also need one to reuse
284
# the passwords entered by the user.
220
285
class PasswordManager(urllib2.HTTPPasswordMgrWithDefaultRealm):
222
287
def __init__(self):
242
307
# handled in the higher levels
243
308
raise errors.InvalidURL(request.get_full_url(), 'no host given.')
245
# We create a connection (but it will not connect yet)
310
# We create a connection (but it will not connect until the first
247
connection = http_connection_class(host)
313
connection = http_connection_class(
314
host, proxied_host=request.proxied_host)
248
315
except httplib.InvalidURL, exception:
249
316
# There is only one occurrence of InvalidURL in httplib
250
317
raise errors.InvalidURL(request.get_full_url(),
368
435
# All other exception are considered connection related.
370
# httplib.HTTPException should indicate a bug
371
# in the urllib implementation, somewhow the
372
# httplib pipeline is in an incorrect state,
373
# we retry in hope that this will correct the
374
# problem but that may need investigation
375
# (note that no such bug is known as of
437
# httplib.HTTPException should indicate a bug in our
438
# urllib-based implementation, somewhow the httplib
439
# pipeline is in an incorrect state, we retry in hope that
440
# this will correct the problem but that may need
441
# investigation (note that no such bug is known as of
376
442
# 20061005 --vila).
378
444
# socket errors generally occurs for reasons
383
449
# FIXME: and then there is HTTPError raised by:
384
450
# - HTTPDefaultErrorHandler (we define our own)
385
451
# - HTTPRedirectHandler.redirect_request
386
# - AbstractDigestAuthHandler.http_error_auth_reqed
388
453
my_exception = errors.ConnectionError(
389
454
msg= 'while sending %s %s:' % (request.get_method(),
498
563
class HTTPSHandler(AbstractHTTPHandler):
499
564
"""A custom handler that just thunks into HTTPSConnection"""
566
https_request = AbstractHTTPHandler.http_request
501
568
def https_open(self, request):
569
connection = request.connection
570
assert isinstance(connection, HTTPSConnection)
571
if connection.sock is None and \
572
connection.proxied_host is not None and \
573
request.get_method() != 'CONNECT' : # Don't loop
574
# FIXME: We need a gazillion connection tests here, but we still
575
# miss a https server :-( :
576
# - with and without proxy
577
# - with and without certificate
578
# - with self-signed certificate
579
# - with and without authentication
580
# - with good and bad credentials (especially the proxy auth aound
582
# - with basic and digest schemes
583
# - reconnection on errors
584
# - connection persistence behaviour (including reconnection)
586
# We are about to connect for the first time via a proxy, we must
587
# issue a CONNECT request first to establish the encrypted link
588
connect = _ConnectRequest(request)
589
response = self.parent.open(connect)
590
if response.code != 200:
591
raise ConnectionError("Can't connect to %s via proxy %s" % (
592
connect.proxied_host, self.host))
594
connection.fake_close()
595
# Establish the connection encryption
596
connection.connect_to_origin()
597
# Propagate the connection to the original request
598
request.connection = connection
502
599
return self.do_open(HTTPSConnection, request)
505
601
class HTTPRedirectHandler(urllib2.HTTPRedirectHandler):
506
602
"""Handles redirect requests.
623
719
class ProxyHandler(urllib2.ProxyHandler):
624
720
"""Handles proxy setting.
626
Copied and modified from urllib2 to be able to modify the
627
request during the request pre-processing instead of
628
modifying it at _open time. As we capture (or create) the
629
connection object during request processing, _open time was
632
Note that the proxy handling *may* modify the protocol used;
633
the request may be against an https server proxied through an
634
http proxy. So, https_request will be called, but later it's
635
really http_open that will be called. This explain why we
636
don't have to call self.parent.open as the urllib2 did.
722
Copied and modified from urllib2 to be able to modify the request during
723
the request pre-processing instead of modifying it at _open time. As we
724
capture (or create) the connection object during request processing, _open
727
The main task is to modify the request so that the connection is done to
728
the proxy while the request still refers to the destination host.
730
Note: the proxy handling *may* modify the protocol used; the request may be
731
against an https server proxied through an http proxy. So, https_request
732
will be called, but later it's really http_open that will be called. This
733
explains why we don't have to call self.parent.open as the urllib2 did.
639
736
# Proxies must be in front
713
810
proxy = self.get_proxy_env_var(type)
714
811
if self._debuglevel > 0:
715
812
print 'set_proxy %s_request for %r' % (type, proxy)
813
# FIXME: python 2.5 urlparse provides a better _parse_proxy which can
814
# grok user:password@host:port as well as
815
# http://user:password@host:port
716
817
# Extract credentials from the url and store them in the
717
818
# password manager so that the proxy AuthHandler can use
781
882
successful and the request authentication parameters have been updated.
886
"""We don't want to retry authenticating endlessly"""
784
888
# The following attributes should be defined by daughter
786
890
# - auth_required_header: the header received from the server
790
894
self.password_manager = password_manager
791
895
self.find_user_password = password_manager.find_user_password
792
896
self.add_password = password_manager.add_password
897
self._retry_count = None
794
899
def update_auth(self, auth, key, value):
795
900
"""Update a value in auth marking the auth as modified if needed"""
805
910
:param headers: The headers for the authentication error response.
806
911
:return: None or the response for the authenticated request.
913
# Don't try to authenticate endlessly
914
if self._retry_count is None:
915
# The retry being recusrsive calls, None identify the first try
916
self._retry_count = 0
918
self._retry_count += 1
919
if self._retry_count > self._max_retry:
920
# Let's be ready for next round
921
self._retry_count = None
808
923
server_header = headers.get(self.auth_required_header, None)
809
924
if server_header is None:
810
925
# The http error MUST have the associated
880
995
:param request: The succesfully authenticated request.
881
996
:param response: The server response (may contain auth info).
998
# It may happen that we need to reconnect later, let's be ready
999
self._retry_count = None
885
1001
def get_password(self, user, authuri, realm=None):
886
1002
"""Ask user for a password if none is already available."""
916
1032
class BasicAuthHandler(AbstractAuthHandler):
917
1033
"""A custom basic authentication handler."""
919
1037
auth_regexp = re.compile('realm="([^"]*)"', re.I)
921
1039
def build_auth_header(self, auth, request):
972
1090
class DigestAuthHandler(AbstractAuthHandler):
973
1091
"""A custom digest authentication handler."""
1093
# Before basic as digest is a bit more secure
975
1096
def auth_params_reusable(self, auth):
976
1097
# If the auth scheme is known, it means a previous
977
1098
# authentication was successful, all information is
1133
1254
instead, we leave our Transport handle them.
1257
accepted_errors = [200, # Ok
1258
206, # Partial content
1261
"""The error codes the caller will handle.
1263
This can be specialized in the request on a case-by case basis, but the
1264
common cases are covered here.
1136
1267
def http_response(self, request, response):
1137
1268
code, msg, hdrs = response.code, response.msg, response.info()
1139
if code not in (200, # Ok
1140
206, # Partial content
1270
accepted_errors = request.accepted_errors
1271
if accepted_errors is None:
1272
accepted_errors = self.accepted_errors
1274
if code not in accepted_errors:
1143
1275
response = self.parent.error('http', request, response,
1144
1276
code, msg, hdrs)
1145
1277
return response
1151
1283
"""Translate common errors into bzr Exceptions"""
1153
1285
def http_error_default(self, req, fp, code, msg, hdrs):
1155
raise errors.NoSuchFile(req.get_selector(),
1156
extra=HTTPError(req.get_full_url(),
1160
1287
raise errors.TransportError('Server refuses to fullfil the request')
1161
1288
elif code == 416:
1162
1289
# We don't know which, but one of the ranges we