22
22
from __future__ import absolute_import
38
import http.client as http_client
40
import httplib as http_client
42
import urllib.request as urllib_request
43
except ImportError: # python < 3
44
import urllib2 as urllib_request
46
from urllib.parse import urljoin, splitport, splittype, splithost
48
from urlparse import urljoin
49
from urllib import splitport, splittype, splithost
51
# TODO: handle_response should be integrated into the http/__init__.py
52
from .response import handle_response
54
# FIXME: Oversimplifying, two kind of exceptions should be
55
# raised, once a request is issued: URLError before we have been
56
# able to process the response, HTTPError after that. Process the
57
# response means we are able to leave the socket clean, so if we
58
# are not able to do that, we should close the connection. The
59
# actual code more or less do that, tests should be written to
62
from ... import __version__ as breezy_version
36
74
from ...bzr.smart import medium
75
from ...sixish import (
37
80
from ...trace import mutter
38
81
from ...transport import (
39
82
ConnectedTransport,
42
# TODO: handle_response should be integrated into the http/__init__.py
43
from .response import handle_response
44
from ._urllib2_wrappers import (
87
_ = (ssl.match_hostname, ssl.CertificateError)
88
except AttributeError:
89
# Provide fallbacks for python < 2.7.9
90
def match_hostname(cert, host):
92
'%s cannot be verified, https certificates verification is only'
93
' available for python versions >= 2.7.9' % (host,))
94
ssl.match_hostname = match_hostname
95
ssl.CertificateError = ValueError
98
# Note for packagers: if there is no package providing certs for your platform,
99
# the curl project produces http://curl.haxx.se/ca/cacert.pem weekly.
100
_ssl_ca_certs_known_locations = [
101
u'/etc/ssl/certs/ca-certificates.crt', # Ubuntu/debian/gentoo
102
u'/etc/pki/tls/certs/ca-bundle.crt', # Fedora/CentOS/RH
103
u'/etc/ssl/ca-bundle.pem', # OpenSuse
104
u'/etc/ssl/cert.pem', # OpenSuse
105
u"/usr/local/share/certs/ca-root-nss.crt", # FreeBSD
106
# XXX: Needs checking, can't trust the interweb ;) -- vila 2012-01-25
107
u'/etc/openssl/certs/ca-certificates.crt', # Solaris
111
def default_ca_certs():
112
if sys.platform == 'win32':
113
return os.path.join(os.path.dirname(sys.executable), u"cacert.pem")
114
elif sys.platform == 'darwin':
115
# FIXME: Needs some default value for osx, waiting for osx installers
116
# guys feedback -- vila 2012-01-25
119
# Try known locations for friendly OSes providing the root certificates
120
# without making them hard to use for any https client.
121
for path in _ssl_ca_certs_known_locations:
122
if os.path.exists(path):
125
# A default path that makes sense and will be mentioned in the error
126
# presented to the user, even if not correct for all platforms
127
return _ssl_ca_certs_known_locations[0]
130
def ca_certs_from_store(path):
131
if not os.path.exists(path):
132
raise ValueError("ca certs path %s does not exist" % path)
136
def cert_reqs_from_store(unicode_str):
139
return {"required": ssl.CERT_REQUIRED,
140
"none": ssl.CERT_NONE}[unicode_str]
142
raise ValueError("invalid value %s" % unicode_str)
145
def default_ca_reqs():
146
if sys.platform in ('win32', 'darwin'):
147
# FIXME: Once we get a native access to root certificates there, this
148
# won't needed anymore. See http://pad.lv/920455 -- vila 2012-02-15
154
opt_ssl_ca_certs = config.Option('ssl.ca_certs',
155
from_unicode=ca_certs_from_store,
156
default=default_ca_certs,
159
Path to certification authority certificates to trust.
161
This should be a valid path to a bundle containing all root Certificate
162
Authorities used to verify an https server certificate.
164
Use ssl.cert_reqs=none to disable certificate verification.
167
opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
168
default=default_ca_reqs,
169
from_unicode=cert_reqs_from_store,
172
Whether to require a certificate from the remote side. (default:required)
175
* none: Certificates ignored
176
* required: Certificates required and validated
179
checked_kerberos = False
183
class addinfourl(urllib_request.addinfourl):
184
'''Replacement addinfourl class compatible with python-2.7's xmlrpclib
186
In python-2.7, xmlrpclib expects that the response object that it receives
187
has a getheader method. http_client.HTTPResponse provides this but
188
urllib_request.addinfourl does not. Add the necessary functions here, ported to
189
use the internal data structures of addinfourl.
192
def getheader(self, name, default=None):
193
if self.headers is None:
194
raise http_client.ResponseNotReady()
195
return self.headers.getheader(name, default)
197
def getheaders(self):
198
if self.headers is None:
199
raise http_client.ResponseNotReady()
200
return list(self.headers.items())
203
class _ReportingFileSocket(object):
205
def __init__(self, filesock, report_activity=None):
206
self.filesock = filesock
207
self._report_activity = report_activity
209
def report_activity(self, size, direction):
210
if self._report_activity:
211
self._report_activity(size, direction)
213
def read(self, size=1):
214
s = self.filesock.read(size)
215
self.report_activity(len(s), 'read')
218
def readline(self, size=-1):
219
s = self.filesock.readline(size)
220
self.report_activity(len(s), 'read')
223
def readinto(self, b):
224
s = self.filesock.readinto(b)
225
self.report_activity(s, 'read')
228
def __getattr__(self, name):
229
return getattr(self.filesock, name)
232
class _ReportingSocket(object):
234
def __init__(self, sock, report_activity=None):
236
self._report_activity = report_activity
238
def report_activity(self, size, direction):
239
if self._report_activity:
240
self._report_activity(size, direction)
242
def sendall(self, s, *args):
243
self.sock.sendall(s, *args)
244
self.report_activity(len(s), 'write')
246
def recv(self, *args):
247
s = self.sock.recv(*args)
248
self.report_activity(len(s), 'read')
251
def makefile(self, mode='r', bufsize=-1):
252
# http_client creates a fileobject that doesn't do buffering, which
253
# makes fp.readline() very expensive because it only reads one byte
254
# at a time. So we wrap the socket in an object that forces
255
# sock.makefile to make a buffered file.
256
fsock = self.sock.makefile(mode, 65536)
257
# And wrap that into a reporting kind of fileobject
258
return _ReportingFileSocket(fsock, self._report_activity)
260
def __getattr__(self, name):
261
return getattr(self.sock, name)
264
# We define our own Response class to keep our http_client pipe clean
265
class Response(http_client.HTTPResponse):
266
"""Custom HTTPResponse, to avoid the need to decorate.
268
http_client prefers to decorate the returned objects, rather
269
than using a custom object.
272
# Some responses have bodies in which we have no interest
273
_body_ignored_responses = [301, 302, 303, 307, 400, 401, 403, 404, 501]
275
# in finish() below, we may have to discard several MB in the worst
276
# case. To avoid buffering that much, we read and discard by chunks
277
# instead. The underlying file is either a socket or a StringIO, so reading
278
# 8k chunks should be fine.
279
_discarded_buf_size = 8192
282
def __init__(self, sock, debuglevel=0, method=None, url=None):
284
super(Response, self).__init__(
285
sock, debuglevel=debuglevel, method=method, url=url)
288
"""Begin to read the response from the server.
290
http_client assumes that some responses get no content and do
291
not even attempt to read the body in that case, leaving
292
the body in the socket, blocking the next request. Let's
293
try to workaround that.
295
http_client.HTTPResponse.begin(self)
296
if self.status in self._body_ignored_responses:
297
if self.debuglevel >= 2:
298
print("For status: [%s], will ready body, length: %s" % (
299
self.status, self.length))
300
if not (self.length is None or self.will_close):
301
# In some cases, we just can't read the body not
302
# even try or we may encounter a 104, 'Connection
303
# reset by peer' error if there is indeed no body
304
# and the server closed the connection just after
305
# having issued the response headers (even if the
306
# headers indicate a Content-Type...)
307
body = self.read(self.length)
308
if self.debuglevel >= 9:
309
# This one can be huge and is generally not interesting
310
print("Consumed body: [%s]" % body)
312
elif self.status == 200:
313
# Whatever the request is, it went ok, so we surely don't want to
314
# close the connection. Some cases are not correctly detected by
315
# http_client.HTTPConnection.getresponse (called by
316
# http_client.HTTPResponse.begin). The CONNECT response for the https
317
# through proxy case is one. Note: the 'will_close' below refers
318
# to the "true" socket between us and the server, whereas the
319
# 'close()' above refers to the copy of that socket created by
320
# http_client for the response itself. So, in the if above we close the
321
# socket to indicate that we are done with the response whereas
322
# below we keep the socket with the server opened.
323
self.will_close = False
326
"""Finish reading the body.
328
In some cases, the client may have left some bytes to read in the
329
body. That will block the next request to succeed if we use a
330
persistent connection. If we don't use a persistent connection, well,
331
nothing will block the next request since a new connection will be
334
:return: the number of bytes left on the socket (may be None)
337
if not self.isclosed():
338
# Make sure nothing was left to be read on the socket
341
while data and self.length:
342
# read() will update self.length
343
data = self.read(min(self.length, self._discarded_buf_size))
346
trace.mutter("%s bytes left on the HTTP socket", pending)
351
# Not inheriting from 'object' because http_client.HTTPConnection doesn't.
352
class AbstractHTTPConnection:
353
"""A custom HTTP(S) Connection, which can reset itself on a bad response"""
355
response_class = Response
357
# When we detect a server responding with the whole file to range requests,
358
# we want to warn. But not below a given thresold.
359
_range_warning_thresold = 1024 * 1024
361
def __init__(self, report_activity=None):
362
self._response = None
363
self._report_activity = report_activity
364
self._ranges_received_whole_file = None
366
def _mutter_connect(self):
367
netloc = '%s:%s' % (self.host, self.port)
368
if self.proxied_host is not None:
369
netloc += '(proxy for %s)' % self.proxied_host
370
trace.mutter('* About to connect() to %s' % netloc)
372
def getresponse(self):
373
"""Capture the response to be able to cleanup"""
374
self._response = http_client.HTTPConnection.getresponse(self)
375
return self._response
377
def cleanup_pipe(self):
378
"""Read the remaining bytes of the last response if any."""
379
if self._response is not None:
381
pending = self._response.finish()
382
# Warn the user (once)
383
if (self._ranges_received_whole_file is None
384
and self._response.status == 200
386
and pending > self._range_warning_thresold):
387
self._ranges_received_whole_file = True
389
'Got a 200 response when asking for multiple ranges,'
390
' does your server at %s:%s support range requests?',
391
self.host, self.port)
392
except socket.error as e:
393
# It's conceivable that the socket is in a bad state here
394
# (including some test cases) and in this case, it doesn't need
395
# cleaning anymore, so no need to fail, we just get rid of the
396
# socket and let callers reconnect
398
or e.args[0] not in (errno.ECONNRESET, errno.ECONNABORTED)):
401
self._response = None
402
# Preserve our preciousss
405
# Let http_client.HTTPConnection do its housekeeping
407
# Restore our preciousss
410
def _wrap_socket_for_reporting(self, sock):
411
"""Wrap the socket before anybody use it."""
412
self.sock = _ReportingSocket(sock, self._report_activity)
415
class HTTPConnection(AbstractHTTPConnection, http_client.HTTPConnection):
417
# XXX: Needs refactoring at the caller level.
418
def __init__(self, host, port=None, proxied_host=None,
419
report_activity=None, ca_certs=None):
420
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
422
http_client.HTTPConnection.__init__(self, host, port)
424
# Use strict=True since we don't support HTTP/0.9
425
http_client.HTTPConnection.__init__(self, host, port, strict=True)
426
self.proxied_host = proxied_host
427
# ca_certs is ignored, it's only relevant for https
430
if 'http' in debug.debug_flags:
431
self._mutter_connect()
432
http_client.HTTPConnection.connect(self)
433
self._wrap_socket_for_reporting(self.sock)
436
class HTTPSConnection(AbstractHTTPConnection, http_client.HTTPSConnection):
438
def __init__(self, host, port=None, key_file=None, cert_file=None,
440
report_activity=None, ca_certs=None):
441
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
443
http_client.HTTPSConnection.__init__(
444
self, host, port, key_file, cert_file)
446
# Use strict=True since we don't support HTTP/0.9
447
http_client.HTTPSConnection.__init__(self, host, port,
448
key_file, cert_file, strict=True)
449
self.proxied_host = proxied_host
450
self.ca_certs = ca_certs
453
if 'http' in debug.debug_flags:
454
self._mutter_connect()
455
http_client.HTTPConnection.connect(self)
456
self._wrap_socket_for_reporting(self.sock)
457
if self.proxied_host is None:
458
self.connect_to_origin()
460
def connect_to_origin(self):
461
# FIXME JRV 2011-12-18: Use location config here?
462
config_stack = config.GlobalStack()
463
cert_reqs = config_stack.get('ssl.cert_reqs')
464
if self.proxied_host is not None:
465
host = self.proxied_host.split(":", 1)[0]
468
if cert_reqs == ssl.CERT_NONE:
469
ui.ui_factory.show_user_warning('not_checking_ssl_cert', host=host)
470
ui.ui_factory.suppressed_warnings.add('not_checking_ssl_cert')
473
if self.ca_certs is None:
474
ca_certs = config_stack.get('ssl.ca_certs')
476
ca_certs = self.ca_certs
479
"No valid trusted SSL CA certificates file set. See "
480
"'brz help ssl.ca_certs' for more information on setting "
483
ssl_context = ssl.create_default_context(
484
purpose=ssl.Purpose.SERVER_AUTH, cafile=ca_certs)
485
ssl.check_hostname = True
487
ssl_context.load_cert_chain(
488
keyfile=self.key_file, certfile=self.cert_file)
489
ssl_context.verify_mode = cert_reqs
490
ssl_sock = ssl_context.wrap_socket(
491
self.sock, server_hostname=self.host)
495
"See `brz help ssl.ca_certs` for how to specify trusted CA"
497
"Pass -Ossl.cert_reqs=none to disable certificate "
498
"verification entirely.\n")
500
# Wrap the ssl socket before anybody use it
501
self._wrap_socket_for_reporting(ssl_sock)
504
class Request(urllib_request.Request):
505
"""A custom Request object.
507
urllib_request determines the request method heuristically (based on
508
the presence or absence of data). We set the method
511
The Request object tracks:
512
- the connection the request will be made on.
513
- the authentication parameters needed to preventively set
514
the authentication header once a first authentication have
518
def __init__(self, method, url, data=None, headers={},
519
origin_req_host=None, unverifiable=False,
520
connection=None, parent=None):
521
urllib_request.Request.__init__(
522
self, url, data, headers,
523
origin_req_host, unverifiable)
525
self.connection = connection
526
# To handle redirections
528
self.redirected_to = None
529
# Unless told otherwise, redirections are not followed
530
self.follow_redirections = False
531
# auth and proxy_auth are dicts containing, at least
532
# (scheme, host, port, realm, user, password, protocol, path).
533
# The dict entries are mostly handled by the AuthHandler.
534
# Some authentication schemes may add more entries.
537
self.proxied_host = None
539
def get_method(self):
542
def set_proxy(self, proxy, type):
543
"""Set the proxy and remember the proxied host."""
545
host, port = splitport(self.host)
547
host, port = splitport(self.get_host())
549
# We need to set the default port ourselves way before it gets set
550
# in the HTTP[S]Connection object at build time.
551
if self.type == 'https':
552
conn_class = HTTPSConnection
554
conn_class = HTTPConnection
555
port = conn_class.default_port
556
self.proxied_host = '%s:%s' % (host, port)
557
urllib_request.Request.set_proxy(self, proxy, type)
558
# When urllib_request makes a https request with our wrapper code and a proxy,
559
# it sets Host to the https proxy, not the host we want to talk to.
560
# I'm fairly sure this is our fault, but what is the cause is an open
561
# question. -- Robert Collins May 8 2010.
562
self.add_unredirected_header('Host', self.proxied_host)
565
class _ConnectRequest(Request):
567
def __init__(self, request):
570
:param request: the first request sent to the proxied host, already
571
processed by the opener (i.e. proxied_host is already set).
573
# We give a fake url and redefine selector or urllib_request will be
575
Request.__init__(self, 'CONNECT', request.get_full_url(),
576
connection=request.connection)
577
if request.proxied_host is None:
578
raise AssertionError()
579
self.proxied_host = request.proxied_host
583
return self.proxied_host
585
def get_selector(self):
588
def set_proxy(self, proxy, type):
589
"""Set the proxy without remembering the proxied host.
591
We already know the proxied host by definition, the CONNECT request
592
occurs only when the connection goes through a proxy. The usual
593
processing (masquerade the request so that the connection is done to
594
the proxy while the request is targeted at another host) does not apply
595
here. In fact, the connection is already established with proxy and we
596
just want to enable the SSL tunneling.
598
urllib_request.Request.set_proxy(self, proxy, type)
601
class ConnectionHandler(urllib_request.BaseHandler):
602
"""Provides connection-sharing by pre-processing requests.
604
urllib_request provides no way to access the HTTPConnection object
605
internally used. But we need it in order to achieve
606
connection sharing. So, we add it to the request just before
607
it is processed, and then we override the do_open method for
608
http[s] requests in AbstractHTTPHandler.
611
handler_order = 1000 # after all pre-processings
613
def __init__(self, report_activity=None, ca_certs=None):
614
self._report_activity = report_activity
615
self.ca_certs = ca_certs
617
def create_connection(self, request, http_connection_class):
620
# Just a bit of paranoia here, this should have been
621
# handled in the higher levels
622
raise urlutils.InvalidURL(request.get_full_url(), 'no host given.')
624
# We create a connection (but it will not connect until the first
627
connection = http_connection_class(
628
host, proxied_host=request.proxied_host,
629
report_activity=self._report_activity,
630
ca_certs=self.ca_certs)
631
except http_client.InvalidURL as exception:
632
# There is only one occurrence of InvalidURL in http_client
633
raise urlutils.InvalidURL(request.get_full_url(),
634
extra='nonnumeric port')
638
def capture_connection(self, request, http_connection_class):
639
"""Capture or inject the request connection.
642
- the request have no connection: create a new one,
644
- the request have a connection: this one have been used
645
already, let's capture it, so that we can give it to
646
another transport to be reused. We don't do that
647
ourselves: the Transport object get the connection from
648
a first request and then propagate it, from request to
649
request or to cloned transports.
651
connection = request.connection
652
if connection is None:
654
connection = self.create_connection(request, http_connection_class)
655
request.connection = connection
657
# All connections will pass here, propagate debug level
658
connection.set_debuglevel(DEBUG)
661
def http_request(self, request):
662
return self.capture_connection(request, HTTPConnection)
664
def https_request(self, request):
665
return self.capture_connection(request, HTTPSConnection)
668
class AbstractHTTPHandler(urllib_request.AbstractHTTPHandler):
669
"""A custom handler for HTTP(S) requests.
671
We overrive urllib_request.AbstractHTTPHandler to get a better
672
control of the connection, the ability to implement new
673
request types and return a response able to cope with
674
persistent connections.
677
# We change our order to be before urllib_request HTTP[S]Handlers
678
# and be chosen instead of them (the first http_open called
682
_default_headers = {'Pragma': 'no-cache',
683
'Cache-control': 'max-age=0',
684
'Connection': 'Keep-Alive',
685
'User-agent': 'Breezy/%s' % breezy_version,
690
urllib_request.AbstractHTTPHandler.__init__(self, debuglevel=DEBUG)
692
def http_request(self, request):
693
"""Common headers setting"""
695
for name, value in self._default_headers.items():
696
if name not in request.headers:
697
request.headers[name] = value
698
# FIXME: We may have to add the Content-Length header if
699
# we have data to send.
702
def retry_or_raise(self, http_class, request, first_try):
703
"""Retry the request (once) or raise the exception.
705
urllib_request raises exception of application level kind, we
706
just have to translate them.
708
http_client can raise exceptions of transport level (badly
709
formatted dialog, loss of connexion or socket level
710
problems). In that case we should issue the request again
711
(http_client will close and reopen a new connection if
714
# When an exception occurs, we give back the original
715
# Traceback or the bugs are hard to diagnose.
716
exc_type, exc_val, exc_tb = sys.exc_info()
717
if exc_type == socket.gaierror:
718
# No need to retry, that will not help
720
origin_req_host = request.origin_req_host
722
origin_req_host = request.get_origin_req_host()
723
raise errors.ConnectionError("Couldn't resolve host '%s'"
726
elif isinstance(exc_val, http_client.ImproperConnectionState):
727
# The http_client pipeline is in incorrect state, it's a bug in our
729
reraise(exc_type, exc_val, exc_tb)
732
if self._debuglevel >= 2:
733
print('Received exception: [%r]' % exc_val)
734
print(' On connection: [%r]' % request.connection)
735
method = request.get_method()
736
url = request.get_full_url()
737
print(' Will retry, %s %r' % (method, url))
738
request.connection.close()
739
response = self.do_open(http_class, request, False)
741
if self._debuglevel >= 2:
742
print('Received second exception: [%r]' % exc_val)
743
print(' On connection: [%r]' % request.connection)
744
if exc_type in (http_client.BadStatusLine, http_client.UnknownProtocol):
745
# http_client.BadStatusLine and
746
# http_client.UnknownProtocol indicates that a
747
# bogus server was encountered or a bad
748
# connection (i.e. transient errors) is
749
# experimented, we have already retried once
750
# for that request so we raise the exception.
751
my_exception = errors.InvalidHttpResponse(
752
request.get_full_url(),
753
'Bad status line received',
755
elif (isinstance(exc_val, socket.error) and len(exc_val.args)
756
and exc_val.args[0] in (errno.ECONNRESET, 10053, 10054)):
757
# 10053 == WSAECONNABORTED
758
# 10054 == WSAECONNRESET
759
raise errors.ConnectionReset(
760
"Connection lost while sending request.")
762
# All other exception are considered connection related.
764
# socket errors generally occurs for reasons
765
# far outside our scope, so closing the
766
# connection and retrying is the best we can
769
selector = request.selector
771
selector = request.get_selector()
772
my_exception = errors.ConnectionError(
773
msg='while sending %s %s:' % (request.get_method(),
777
if self._debuglevel >= 2:
778
print('On connection: [%r]' % request.connection)
779
method = request.get_method()
780
url = request.get_full_url()
781
print(' Failed again, %s %r' % (method, url))
782
print(' Will raise: [%r]' % my_exception)
783
reraise(type(my_exception), my_exception, exc_tb)
786
def do_open(self, http_class, request, first_try=True):
787
"""See urllib_request.AbstractHTTPHandler.do_open for the general idea.
789
The request will be retried once if it fails.
791
connection = request.connection
792
if connection is None:
793
raise AssertionError(
794
'Cannot process a request without a connection')
796
# Get all the headers
798
headers.update(request.header_items())
799
headers.update(request.unredirected_hdrs)
800
# Some servers or proxies will choke on headers not properly
801
# cased. http_client/urllib/urllib_request all use capitalize to get canonical
802
# header names, but only python2.5 urllib_request use title() to fix them just
803
# before sending the request. And not all versions of python 2.5 do
804
# that. Since we replace urllib_request.AbstractHTTPHandler.do_open we do it
806
headers = {name.title(): val for name, val in headers.items()}
809
method = request.get_method()
811
url = request.selector
813
url = request.get_selector()
814
if sys.version_info[:2] >= (3, 6):
815
connection._send_request(method, url,
816
# FIXME: implements 100-continue
817
# None, # We don't send the body yet
819
headers, encode_chunked=False)
821
connection._send_request(method, url,
822
# FIXME: implements 100-continue
823
# None, # We don't send the body yet
826
if 'http' in debug.debug_flags:
827
trace.mutter('> %s %s' % (method, url))
829
for k, v in headers.items():
830
# People are often told to paste -Dhttp output to help
831
# debug. Don't compromise credentials.
832
if k in ('Authorization', 'Proxy-Authorization'):
834
hdrs.append('%s: %s' % (k, v))
835
trace.mutter('> ' + '\n> '.join(hdrs) + '\n')
836
if self._debuglevel >= 1:
837
print('Request sent: [%r] from (%s)'
838
% (request, request.connection.sock.getsockname()))
839
response = connection.getresponse()
840
convert_to_addinfourl = True
841
except (ssl.SSLError, ssl.CertificateError):
842
# Something is wrong with either the certificate or the hostname,
843
# re-trying won't help
845
except (socket.gaierror, http_client.BadStatusLine, http_client.UnknownProtocol,
846
socket.error, http_client.HTTPException):
847
response = self.retry_or_raise(http_class, request, first_try)
848
convert_to_addinfourl = False
851
response.msg = response.reason
854
# FIXME: HTTPConnection does not fully support 100-continue (the
855
# server responses are just ignored)
858
# mutter('Will send the body')
859
# # We can send the body now
860
# body = request.data
862
# raise URLError("No data given")
863
# connection.send(body)
864
# response = connection.getresponse()
866
if self._debuglevel >= 2:
867
print('Receives response: %r' % response)
868
print(' For: %r(%r)' % (request.get_method(),
869
request.get_full_url()))
871
if convert_to_addinfourl:
872
# Shamelessly copied from urllib_request
876
fp = socket._fileobject(r, bufsize=65536)
877
resp = addinfourl(fp, r.msg, req.get_full_url())
880
resp.version = r.version
881
if self._debuglevel >= 2:
882
print('Create addinfourl: %r' % resp)
883
print(' For: %r(%r)' % (request.get_method(),
884
request.get_full_url()))
885
if 'http' in debug.debug_flags:
886
version = 'HTTP/%d.%d'
888
version = version % (resp.version / 10,
891
version = 'HTTP/%r' % resp.version
892
trace.mutter('< %s %s %s' % (version, resp.code,
894
# Use the raw header lines instead of treating resp.info() as a
895
# dict since we may miss duplicated headers otherwise.
896
hdrs = [h.rstrip('\r\n') for h in resp.info().headers]
897
trace.mutter('< ' + '\n< '.join(hdrs) + '\n')
903
class HTTPHandler(AbstractHTTPHandler):
904
"""A custom handler that just thunks into HTTPConnection"""
906
def http_open(self, request):
907
return self.do_open(HTTPConnection, request)
910
class HTTPSHandler(AbstractHTTPHandler):
911
"""A custom handler that just thunks into HTTPSConnection"""
913
https_request = AbstractHTTPHandler.http_request
915
def https_open(self, request):
916
connection = request.connection
917
if connection.sock is None and \
918
connection.proxied_host is not None and \
919
request.get_method() != 'CONNECT': # Don't loop
920
# FIXME: We need a gazillion connection tests here, but we still
921
# miss a https server :-( :
922
# - with and without proxy
923
# - with and without certificate
924
# - with self-signed certificate
925
# - with and without authentication
926
# - with good and bad credentials (especially the proxy auth around
928
# - with basic and digest schemes
929
# - reconnection on errors
930
# - connection persistence behaviour (including reconnection)
932
# We are about to connect for the first time via a proxy, we must
933
# issue a CONNECT request first to establish the encrypted link
934
connect = _ConnectRequest(request)
935
response = self.parent.open(connect)
936
if response.code != 200:
937
raise errors.ConnectionError("Can't connect to %s via proxy %s" % (
938
connect.proxied_host, self.host))
940
connection.cleanup_pipe()
941
# Establish the connection encryption
942
connection.connect_to_origin()
943
# Propagate the connection to the original request
944
request.connection = connection
945
return self.do_open(HTTPSConnection, request)
948
class HTTPRedirectHandler(urllib_request.HTTPRedirectHandler):
949
"""Handles redirect requests.
951
We have to implement our own scheme because we use a specific
952
Request object and because we want to implement a specific
956
# RFC2616 says that only read requests should be redirected
957
# without interacting with the user. But Breezy uses some
958
# shortcuts to optimize against roundtrips which can leads to
959
# write requests being issued before read requests of
960
# containing dirs can be redirected. So we redirect write
961
# requests in the same way which seems to respect the spirit
962
# of the RFC if not its letter.
964
def redirect_request(self, req, fp, code, msg, headers, newurl):
965
"""See urllib_request.HTTPRedirectHandler.redirect_request"""
966
# We would have preferred to update the request instead
967
# of creating a new one, but the urllib_request.Request object
968
# has a too complicated creation process to provide a
969
# simple enough equivalent update process. Instead, when
970
# redirecting, we only update the following request in
971
# the redirect chain with a reference to the parent
974
# Some codes make no sense in our context and are treated
977
# 300: Multiple choices for different representations of
978
# the URI. Using that mechanisn with Breezy will violate the
979
# protocol neutrality of Transport.
981
# 304: Not modified (SHOULD only occurs with conditional
982
# GETs which are not used by our implementation)
984
# 305: Use proxy. I can't imagine this one occurring in
985
# our context-- vila/20060909
987
# 306: Unused (if the RFC says so...)
989
# If the code is 302 and the request is HEAD, some may
990
# think that it is a sufficent hint that the file exists
991
# and that we MAY avoid following the redirections. But
992
# if we want to be sure, we MUST follow them.
995
origin_req_host = req.origin_req_host
997
origin_req_host = req.get_origin_req_host()
999
if code in (301, 302, 303, 307):
1000
return Request(req.get_method(), newurl,
1001
headers=req.headers,
1002
origin_req_host=origin_req_host,
1004
# TODO: It will be nice to be able to
1005
# detect virtual hosts sharing the same
1006
# IP address, that will allow us to
1007
# share the same connection...
1012
raise urllib_request.HTTPError(
1013
req.get_full_url(), code, msg, headers, fp)
1015
def http_error_302(self, req, fp, code, msg, headers):
1016
"""Requests the redirected to URI.
1018
Copied from urllib_request to be able to clean the pipe of the associated
1019
connection, *before* issuing the redirected request but *after* having
1020
eventually raised an error.
1022
# Some servers (incorrectly) return multiple Location headers
1023
# (so probably same goes for URI). Use first header.
1025
# TODO: Once we get rid of addinfourl objects, the
1026
# following will need to be updated to use correct case
1028
if 'location' in headers:
1029
newurl = headers.get('location')
1030
elif 'uri' in headers:
1031
newurl = headers.get('uri')
1034
if self._debuglevel >= 1:
1035
print('Redirected to: %s (followed: %r)' % (newurl,
1036
req.follow_redirections))
1037
if req.follow_redirections is False:
1038
req.redirected_to = newurl
1041
newurl = urljoin(req.get_full_url(), newurl)
1043
# This call succeeds or raise an error. urllib_request returns
1044
# if redirect_request returns None, but our
1045
# redirect_request never returns None.
1046
redirected_req = self.redirect_request(req, fp, code, msg, headers,
1050
# .redirect_dict has a key url if url was previously visited.
1051
if hasattr(req, 'redirect_dict'):
1052
visited = redirected_req.redirect_dict = req.redirect_dict
1053
if (visited.get(newurl, 0) >= self.max_repeats or
1054
len(visited) >= self.max_redirections):
1055
raise urllib_request.HTTPError(req.get_full_url(), code,
1056
self.inf_msg + msg, headers, fp)
1058
visited = redirected_req.redirect_dict = req.redirect_dict = {}
1059
visited[newurl] = visited.get(newurl, 0) + 1
1061
# We can close the fp now that we are sure that we won't
1062
# use it with HTTPError.
1064
# We have all we need already in the response
1065
req.connection.cleanup_pipe()
1067
return self.parent.open(redirected_req)
1069
http_error_301 = http_error_303 = http_error_307 = http_error_302
1072
class ProxyHandler(urllib_request.ProxyHandler):
1073
"""Handles proxy setting.
1075
Copied and modified from urllib_request to be able to modify the request during
1076
the request pre-processing instead of modifying it at _open time. As we
1077
capture (or create) the connection object during request processing, _open
1080
The main task is to modify the request so that the connection is done to
1081
the proxy while the request still refers to the destination host.
1083
Note: the proxy handling *may* modify the protocol used; the request may be
1084
against an https server proxied through an http proxy. So, https_request
1085
will be called, but later it's really http_open that will be called. This
1086
explains why we don't have to call self.parent.open as the urllib_request did.
1089
# Proxies must be in front
1093
def __init__(self, proxies=None):
1094
urllib_request.ProxyHandler.__init__(self, proxies)
1095
# First, let's get rid of urllib_request implementation
1096
for type, proxy in self.proxies.items():
1097
if self._debuglevel >= 3:
1098
print('Will unbind %s_open for %r' % (type, proxy))
1099
delattr(self, '%s_open' % type)
1101
def bind_scheme_request(proxy, scheme):
1104
scheme_request = scheme + '_request'
1105
if self._debuglevel >= 3:
1106
print('Will bind %s for %r' % (scheme_request, proxy))
1107
setattr(self, scheme_request,
1108
lambda request: self.set_proxy(request, scheme))
1109
# We are interested only by the http[s] proxies
1110
http_proxy = self.get_proxy_env_var('http')
1111
bind_scheme_request(http_proxy, 'http')
1112
https_proxy = self.get_proxy_env_var('https')
1113
bind_scheme_request(https_proxy, 'https')
1115
def get_proxy_env_var(self, name, default_to='all'):
1116
"""Get a proxy env var.
1118
Note that we indirectly rely on
1119
urllib.getproxies_environment taking into account the
1120
uppercased values for proxy variables.
1123
return self.proxies[name.lower()]
1125
if default_to is not None:
1126
# Try to get the alternate environment variable
1128
return self.proxies[default_to]
1133
def proxy_bypass(self, host):
1134
"""Check if host should be proxied or not.
1136
:returns: True to skip the proxy, False otherwise.
1138
no_proxy = self.get_proxy_env_var('no', default_to=None)
1139
bypass = self.evaluate_proxy_bypass(host, no_proxy)
1141
# Nevertheless, there are platform-specific ways to
1143
return urllib.proxy_bypass(host)
1147
def evaluate_proxy_bypass(self, host, no_proxy):
1148
"""Check the host against a comma-separated no_proxy list as a string.
1150
:param host: ``host:port`` being requested
1152
:param no_proxy: comma-separated list of hosts to access directly.
1154
:returns: True to skip the proxy, False not to, or None to
1157
if no_proxy is None:
1158
# All hosts are proxied
1160
hhost, hport = splitport(host)
1161
# Does host match any of the domains mentioned in
1162
# no_proxy ? The rules about what is authorized in no_proxy
1163
# are fuzzy (to say the least). We try to allow most
1164
# commonly seen values.
1165
for domain in no_proxy.split(','):
1166
domain = domain.strip()
1169
dhost, dport = splitport(domain)
1170
if hport == dport or dport is None:
1171
# Protect glob chars
1172
dhost = dhost.replace(".", r"\.")
1173
dhost = dhost.replace("*", r".*")
1174
dhost = dhost.replace("?", r".")
1175
if re.match(dhost, hhost, re.IGNORECASE):
1177
# Nothing explicitly avoid the host
1180
def set_proxy(self, request, type):
1184
host = request.get_host()
1185
if self.proxy_bypass(host):
1188
proxy = self.get_proxy_env_var(type)
1189
if self._debuglevel >= 3:
1190
print('set_proxy %s_request for %r' % (type, proxy))
1191
# FIXME: python 2.5 urlparse provides a better _parse_proxy which can
1192
# grok user:password@host:port as well as
1193
# http://user:password@host:port
1195
parsed_url = transport.ConnectedTransport._split_url(proxy)
1196
if not parsed_url.host:
1197
raise urlutils.InvalidURL(proxy, 'No host component')
1199
if request.proxy_auth == {}:
1200
# No proxy auth parameter are available, we are handling the first
1201
# proxied request, intialize. scheme (the authentication scheme)
1202
# and realm will be set by the AuthHandler
1203
request.proxy_auth = {
1204
'host': parsed_url.host,
1205
'port': parsed_url.port,
1206
'user': parsed_url.user,
1207
'password': parsed_url.password,
1208
'protocol': parsed_url.scheme,
1209
# We ignore path since we connect to a proxy
1211
if parsed_url.port is None:
1212
phost = parsed_url.host
1214
phost = parsed_url.host + ':%d' % parsed_url.port
1215
request.set_proxy(phost, type)
1216
if self._debuglevel >= 3:
1217
print('set_proxy: proxy set to %s://%s' % (type, phost))
1221
class AbstractAuthHandler(urllib_request.BaseHandler):
1222
"""A custom abstract authentication handler for all http authentications.
1224
Provides the meat to handle authentication errors and
1225
preventively set authentication headers after the first
1226
successful authentication.
1228
This can be used for http and proxy, as well as for basic, negotiate and
1229
digest authentications.
1231
This provides an unified interface for all authentication handlers
1232
(urllib_request provides far too many with different policies).
1234
The interaction between this handler and the urllib_request
1235
framework is not obvious, it works as follow:
1237
opener.open(request) is called:
1239
- that may trigger http_request which will add an authentication header
1240
(self.build_header) if enough info is available.
1242
- the request is sent to the server,
1244
- if an authentication error is received self.auth_required is called,
1245
we acquire the authentication info in the error headers and call
1246
self.auth_match to check that we are able to try the
1247
authentication and complete the authentication parameters,
1249
- we call parent.open(request), that may trigger http_request
1250
and will add a header (self.build_header), but here we have
1251
all the required info (keep in mind that the request and
1252
authentication used in the recursive calls are really (and must be)
1253
the *same* objects).
1255
- if the call returns a response, the authentication have been
1256
successful and the request authentication parameters have been updated.
1260
"""The scheme as it appears in the server header (lower cased)"""
1263
"""We don't want to retry authenticating endlessly"""
1265
requires_username = True
1266
"""Whether the auth mechanism requires a username."""
1268
# The following attributes should be defined by daughter
1270
# - auth_required_header: the header received from the server
1271
# - auth_header: the header sent in the request
1274
# We want to know when we enter into an try/fail cycle of
1275
# authentications so we initialize to None to indicate that we aren't
1276
# in such a cycle by default.
1277
self._retry_count = None
1279
def _parse_auth_header(self, server_header):
1280
"""Parse the authentication header.
1282
:param server_header: The value of the header sent by the server
1283
describing the authenticaion request.
1285
:return: A tuple (scheme, remainder) scheme being the first word in the
1286
given header (lower cased), remainder may be None.
1289
scheme, remainder = server_header.split(None, 1)
1291
scheme = server_header
1293
return (scheme.lower(), remainder)
1295
def update_auth(self, auth, key, value):
1296
"""Update a value in auth marking the auth as modified if needed"""
1297
old_value = auth.get(key, None)
1298
if old_value != value:
1300
auth['modified'] = True
1302
def auth_required(self, request, headers):
1303
"""Retry the request if the auth scheme is ours.
1305
:param request: The request needing authentication.
1306
:param headers: The headers for the authentication error response.
1307
:return: None or the response for the authenticated request.
1309
# Don't try to authenticate endlessly
1310
if self._retry_count is None:
1311
# The retry being recusrsive calls, None identify the first retry
1312
self._retry_count = 1
1314
self._retry_count += 1
1315
if self._retry_count > self._max_retry:
1316
# Let's be ready for next round
1317
self._retry_count = None
1320
server_headers = headers.get_all(self.auth_required_header)
1322
server_headers = headers.getheaders(self.auth_required_header)
1323
if not server_headers:
1324
# The http error MUST have the associated
1325
# header. This must never happen in production code.
1326
raise KeyError('%s not found' % self.auth_required_header)
1328
auth = self.get_auth(request)
1329
auth['modified'] = False
1330
# Put some common info in auth if the caller didn't
1331
if auth.get('path', None) is None:
1332
parsed_url = urlutils.URL.from_string(request.get_full_url())
1333
self.update_auth(auth, 'protocol', parsed_url.scheme)
1334
self.update_auth(auth, 'host', parsed_url.host)
1335
self.update_auth(auth, 'port', parsed_url.port)
1336
self.update_auth(auth, 'path', parsed_url.path)
1337
# FIXME: the auth handler should be selected at a single place instead
1338
# of letting all handlers try to match all headers, but the current
1339
# design doesn't allow a simple implementation.
1340
for server_header in server_headers:
1341
# Several schemes can be proposed by the server, try to match each
1343
matching_handler = self.auth_match(server_header, auth)
1344
if matching_handler:
1345
# auth_match may have modified auth (by adding the
1346
# password or changing the realm, for example)
1347
if (request.get_header(self.auth_header, None) is not None
1348
and not auth['modified']):
1349
# We already tried that, give up
1352
# Only the most secure scheme proposed by the server should be
1353
# used, since the handlers use 'handler_order' to describe that
1354
# property, the first handler tried takes precedence, the
1355
# others should not attempt to authenticate if the best one
1357
best_scheme = auth.get('best_scheme', None)
1358
if best_scheme is None:
1359
# At that point, if current handler should doesn't succeed
1360
# the credentials are wrong (or incomplete), but we know
1361
# that the associated scheme should be used.
1362
best_scheme = auth['best_scheme'] = self.scheme
1363
if best_scheme != self.scheme:
1366
if self.requires_username and auth.get('user', None) is None:
1367
# Without a known user, we can't authenticate
1371
request.connection.cleanup_pipe()
1372
# Retry the request with an authentication header added
1373
response = self.parent.open(request)
1375
self.auth_successful(request, response)
1377
# We are not qualified to handle the authentication.
1378
# Note: the authentication error handling will try all
1379
# available handlers. If one of them authenticates
1380
# successfully, a response will be returned. If none of
1381
# them succeeds, None will be returned and the error
1382
# handler will raise the 401 'Unauthorized' or the 407
1383
# 'Proxy Authentication Required' error.
1386
def add_auth_header(self, request, header):
1387
"""Add the authentication header to the request"""
1388
request.add_unredirected_header(self.auth_header, header)
1390
def auth_match(self, header, auth):
1391
"""Check that we are able to handle that authentication scheme.
1393
The request authentication parameters may need to be
1394
updated with info from the server. Some of these
1395
parameters, when combined, are considered to be the
1396
authentication key, if one of them change the
1397
authentication result may change. 'user' and 'password'
1398
are exampls, but some auth schemes may have others
1399
(digest's nonce is an example, digest's nonce_count is a
1400
*counter-example*). Such parameters must be updated by
1401
using the update_auth() method.
1403
:param header: The authentication header sent by the server.
1404
:param auth: The auth parameters already known. They may be
1406
:returns: True if we can try to handle the authentication.
1408
raise NotImplementedError(self.auth_match)
1410
def build_auth_header(self, auth, request):
1411
"""Build the value of the header used to authenticate.
1413
:param auth: The auth parameters needed to build the header.
1414
:param request: The request needing authentication.
1416
:return: None or header.
1418
raise NotImplementedError(self.build_auth_header)
1420
def auth_successful(self, request, response):
1421
"""The authentification was successful for the request.
1423
Additional infos may be available in the response.
1425
:param request: The succesfully authenticated request.
1426
:param response: The server response (may contain auth info).
1428
# It may happen that we need to reconnect later, let's be ready
1429
self._retry_count = None
1431
def get_user_password(self, auth):
1432
"""Ask user for a password if none is already available.
1434
:param auth: authentication info gathered so far (from the initial url
1435
and then during dialog with the server).
1437
auth_conf = config.AuthenticationConfig()
1438
user = auth.get('user', None)
1439
password = auth.get('password', None)
1440
realm = auth['realm']
1441
port = auth.get('port', None)
1444
user = auth_conf.get_user(auth['protocol'], auth['host'],
1445
port=port, path=auth['path'],
1446
realm=realm, ask=True,
1447
prompt=self.build_username_prompt(auth))
1448
if user is not None and password is None:
1449
password = auth_conf.get_password(
1450
auth['protocol'], auth['host'], user,
1452
path=auth['path'], realm=realm,
1453
prompt=self.build_password_prompt(auth))
1455
return user, password
1457
def _build_password_prompt(self, auth):
1458
"""Build a prompt taking the protocol used into account.
1460
The AuthHandler is used by http and https, we want that information in
1461
the prompt, so we build the prompt from the authentication dict which
1462
contains all the needed parts.
1464
Also, http and proxy AuthHandlers present different prompts to the
1465
user. The daughter classes should implements a public
1466
build_password_prompt using this method.
1468
prompt = u'%s' % auth['protocol'].upper() + u' %(user)s@%(host)s'
1469
realm = auth['realm']
1470
if realm is not None:
1471
prompt += u", Realm: '%s'" % realm
1472
prompt += u' password'
1475
def _build_username_prompt(self, auth):
1476
"""Build a prompt taking the protocol used into account.
1478
The AuthHandler is used by http and https, we want that information in
1479
the prompt, so we build the prompt from the authentication dict which
1480
contains all the needed parts.
1482
Also, http and proxy AuthHandlers present different prompts to the
1483
user. The daughter classes should implements a public
1484
build_username_prompt using this method.
1486
prompt = u'%s' % auth['protocol'].upper() + u' %(host)s'
1487
realm = auth['realm']
1488
if realm is not None:
1489
prompt += u", Realm: '%s'" % realm
1490
prompt += u' username'
1493
def http_request(self, request):
1494
"""Insert an authentication header if information is available"""
1495
auth = self.get_auth(request)
1496
if self.auth_params_reusable(auth):
1497
self.add_auth_header(
1498
request, self.build_auth_header(auth, request))
1501
https_request = http_request # FIXME: Need test
1504
class NegotiateAuthHandler(AbstractAuthHandler):
1505
"""A authentication handler that handles WWW-Authenticate: Negotiate.
1507
At the moment this handler supports just Kerberos. In the future,
1508
NTLM support may also be added.
1511
scheme = 'negotiate'
1513
requires_username = False
1515
def auth_match(self, header, auth):
1516
scheme, raw_auth = self._parse_auth_header(header)
1517
if scheme != self.scheme:
1519
self.update_auth(auth, 'scheme', scheme)
1520
resp = self._auth_match_kerberos(auth)
1523
# Optionally should try to authenticate using NTLM here
1524
self.update_auth(auth, 'negotiate_response', resp)
1527
def _auth_match_kerberos(self, auth):
1528
"""Try to create a GSSAPI response for authenticating against a host."""
1529
global kerberos, checked_kerberos
1530
if kerberos is None and not checked_kerberos:
1535
checked_kerberos = True
1536
if kerberos is None:
1538
ret, vc = kerberos.authGSSClientInit("HTTP@%(host)s" % auth)
1540
trace.warning('Unable to create GSSAPI context for %s: %d',
1543
ret = kerberos.authGSSClientStep(vc, "")
1545
trace.mutter('authGSSClientStep failed: %d', ret)
1547
return kerberos.authGSSClientResponse(vc)
1549
def build_auth_header(self, auth, request):
1550
return "Negotiate %s" % auth['negotiate_response']
1552
def auth_params_reusable(self, auth):
1553
# If the auth scheme is known, it means a previous
1554
# authentication was successful, all information is
1555
# available, no further checks are needed.
1556
return (auth.get('scheme', None) == 'negotiate' and
1557
auth.get('negotiate_response', None) is not None)
1560
class BasicAuthHandler(AbstractAuthHandler):
1561
"""A custom basic authentication handler."""
1565
auth_regexp = re.compile('realm="([^"]*)"', re.I)
1567
def build_auth_header(self, auth, request):
1568
raw = '%s:%s' % (auth['user'], auth['password'])
1569
auth_header = 'Basic ' + \
1570
base64.b64encode(raw.encode('utf-8')).decode('ascii')
1573
def extract_realm(self, header_value):
1574
match = self.auth_regexp.search(header_value)
1577
realm = match.group(1)
1580
def auth_match(self, header, auth):
1581
scheme, raw_auth = self._parse_auth_header(header)
1582
if scheme != self.scheme:
1585
match, realm = self.extract_realm(raw_auth)
1587
# Put useful info into auth
1588
self.update_auth(auth, 'scheme', scheme)
1589
self.update_auth(auth, 'realm', realm)
1590
if (auth.get('user', None) is None
1591
or auth.get('password', None) is None):
1592
user, password = self.get_user_password(auth)
1593
self.update_auth(auth, 'user', user)
1594
self.update_auth(auth, 'password', password)
1595
return match is not None
1597
def auth_params_reusable(self, auth):
1598
# If the auth scheme is known, it means a previous
1599
# authentication was successful, all information is
1600
# available, no further checks are needed.
1601
return auth.get('scheme', None) == 'basic'
1604
def get_digest_algorithm_impls(algorithm):
1607
if algorithm == 'MD5':
1608
def H(x): return osutils.md5(x).hexdigest()
1609
elif algorithm == 'SHA':
1610
H = osutils.sha_string
1612
def KD(secret, data): return H(
1613
("%s:%s" % (secret, data)).encode('utf-8'))
1617
def get_new_cnonce(nonce, nonce_count):
1618
raw = '%s:%d:%s:%s' % (nonce, nonce_count, time.ctime(),
1619
osutils.rand_chars(8))
1620
return osutils.sha_string(raw.encode('utf-8'))[:16]
1623
class DigestAuthHandler(AbstractAuthHandler):
1624
"""A custom digest authentication handler."""
1627
# Before basic as digest is a bit more secure and should be preferred
1630
def auth_params_reusable(self, auth):
1631
# If the auth scheme is known, it means a previous
1632
# authentication was successful, all information is
1633
# available, no further checks are needed.
1634
return auth.get('scheme', None) == 'digest'
1636
def auth_match(self, header, auth):
1637
scheme, raw_auth = self._parse_auth_header(header)
1638
if scheme != self.scheme:
1641
# Put the requested authentication info into a dict
1642
req_auth = urllib_request.parse_keqv_list(
1643
urllib_request.parse_http_list(raw_auth))
1645
# Check that we can handle that authentication
1646
qop = req_auth.get('qop', None)
1647
if qop != 'auth': # No auth-int so far
1650
H, KD = get_digest_algorithm_impls(req_auth.get('algorithm', 'MD5'))
1654
realm = req_auth.get('realm', None)
1655
# Put useful info into auth
1656
self.update_auth(auth, 'scheme', scheme)
1657
self.update_auth(auth, 'realm', realm)
1658
if auth.get('user', None) is None or auth.get('password', None) is None:
1659
user, password = self.get_user_password(auth)
1660
self.update_auth(auth, 'user', user)
1661
self.update_auth(auth, 'password', password)
1664
if req_auth.get('algorithm', None) is not None:
1665
self.update_auth(auth, 'algorithm', req_auth.get('algorithm'))
1666
nonce = req_auth['nonce']
1667
if auth.get('nonce', None) != nonce:
1668
# A new nonce, never used
1669
self.update_auth(auth, 'nonce_count', 0)
1670
self.update_auth(auth, 'nonce', nonce)
1671
self.update_auth(auth, 'qop', qop)
1672
auth['opaque'] = req_auth.get('opaque', None)
1674
# Some required field is not there
1679
def build_auth_header(self, auth, request):
1681
selector = request.selector
1683
selector = request.get_selector()
1684
url_scheme, url_selector = splittype(selector)
1685
sel_host, uri = splithost(url_selector)
1688
(auth['user'], auth['realm'], auth['password'])).encode('utf-8')
1689
A2 = ('%s:%s' % (request.get_method(), uri)).encode('utf-8')
1691
nonce = auth['nonce']
1694
nonce_count = auth['nonce_count'] + 1
1695
ncvalue = '%08x' % nonce_count
1696
cnonce = get_new_cnonce(nonce, nonce_count)
1698
H, KD = get_digest_algorithm_impls(auth.get('algorithm', 'MD5'))
1699
nonce_data = '%s:%s:%s:%s:%s' % (nonce, ncvalue, cnonce, qop, H(A2))
1700
request_digest = KD(H(A1), nonce_data)
1703
header += 'username="%s", realm="%s", nonce="%s"' % (auth['user'],
1706
header += ', uri="%s"' % uri
1707
header += ', cnonce="%s", nc=%s' % (cnonce, ncvalue)
1708
header += ', qop="%s"' % qop
1709
header += ', response="%s"' % request_digest
1710
# Append the optional fields
1711
opaque = auth.get('opaque', None)
1713
header += ', opaque="%s"' % opaque
1714
if auth.get('algorithm', None):
1715
header += ', algorithm="%s"' % auth.get('algorithm')
1717
# We have used the nonce once more, update the count
1718
auth['nonce_count'] = nonce_count
1723
class HTTPAuthHandler(AbstractAuthHandler):
1724
"""Custom http authentication handler.
1726
Send the authentication preventively to avoid the roundtrip
1727
associated with the 401 error and keep the revelant info in
1728
the auth request attribute.
1731
auth_required_header = 'www-authenticate'
1732
auth_header = 'Authorization'
1734
def get_auth(self, request):
1735
"""Get the auth params from the request"""
1738
def set_auth(self, request, auth):
1739
"""Set the auth params for the request"""
1742
def build_password_prompt(self, auth):
1743
return self._build_password_prompt(auth)
1745
def build_username_prompt(self, auth):
1746
return self._build_username_prompt(auth)
1748
def http_error_401(self, req, fp, code, msg, headers):
1749
return self.auth_required(req, headers)
1752
class ProxyAuthHandler(AbstractAuthHandler):
1753
"""Custom proxy authentication handler.
1755
Send the authentication preventively to avoid the roundtrip
1756
associated with the 407 error and keep the revelant info in
1757
the proxy_auth request attribute..
1760
auth_required_header = 'proxy-authenticate'
1761
# FIXME: the correct capitalization is Proxy-Authorization,
1762
# but python-2.4 urllib_request.Request insist on using capitalize()
1763
# instead of title().
1764
auth_header = 'Proxy-authorization'
1766
def get_auth(self, request):
1767
"""Get the auth params from the request"""
1768
return request.proxy_auth
1770
def set_auth(self, request, auth):
1771
"""Set the auth params for the request"""
1772
request.proxy_auth = auth
1774
def build_password_prompt(self, auth):
1775
prompt = self._build_password_prompt(auth)
1776
prompt = u'Proxy ' + prompt
1779
def build_username_prompt(self, auth):
1780
prompt = self._build_username_prompt(auth)
1781
prompt = u'Proxy ' + prompt
1784
def http_error_407(self, req, fp, code, msg, headers):
1785
return self.auth_required(req, headers)
1788
class HTTPBasicAuthHandler(BasicAuthHandler, HTTPAuthHandler):
1789
"""Custom http basic authentication handler"""
1792
class ProxyBasicAuthHandler(BasicAuthHandler, ProxyAuthHandler):
1793
"""Custom proxy basic authentication handler"""
1796
class HTTPDigestAuthHandler(DigestAuthHandler, HTTPAuthHandler):
1797
"""Custom http basic authentication handler"""
1800
class ProxyDigestAuthHandler(DigestAuthHandler, ProxyAuthHandler):
1801
"""Custom proxy basic authentication handler"""
1804
class HTTPNegotiateAuthHandler(NegotiateAuthHandler, HTTPAuthHandler):
1805
"""Custom http negotiate authentication handler"""
1808
class ProxyNegotiateAuthHandler(NegotiateAuthHandler, ProxyAuthHandler):
1809
"""Custom proxy negotiate authentication handler"""
1812
class HTTPErrorProcessor(urllib_request.HTTPErrorProcessor):
1813
"""Process HTTP error responses.
1815
We don't really process the errors, quite the contrary
1816
instead, we leave our Transport handle them.
1819
accepted_errors = [200, # Ok
1820
206, # Partial content
1826
"""The error codes the caller will handle.
1828
This can be specialized in the request on a case-by case basis, but the
1829
common cases are covered here.
1832
def http_response(self, request, response):
1833
code, msg, hdrs = response.code, response.msg, response.info()
1835
if code not in self.accepted_errors:
1836
response = self.parent.error('http', request, response,
1840
https_response = http_response
1843
class HTTPDefaultErrorHandler(urllib_request.HTTPDefaultErrorHandler):
1844
"""Translate common errors into Breezy Exceptions"""
1846
def http_error_default(self, req, fp, code, msg, hdrs):
1848
raise errors.TransportError(
1849
'Server refuses to fulfill the request (403 Forbidden)'
1850
' for %s' % req.get_full_url())
1852
raise errors.InvalidHttpResponse(req.get_full_url(),
1853
'Unable to handle http code %d: %s'
1857
class Opener(object):
1858
"""A wrapper around urllib_request.build_opener
1860
Daughter classes can override to build their own specific opener
1862
# TODO: Provides hooks for daughter classes.
1865
connection=ConnectionHandler,
1866
redirect=HTTPRedirectHandler,
1867
error=HTTPErrorProcessor,
1868
report_activity=None,
1870
self._opener = urllib_request.build_opener(
1871
connection(report_activity=report_activity, ca_certs=ca_certs),
1874
HTTPBasicAuthHandler(),
1875
HTTPDigestAuthHandler(),
1876
HTTPNegotiateAuthHandler(),
1877
ProxyBasicAuthHandler(),
1878
ProxyDigestAuthHandler(),
1879
ProxyNegotiateAuthHandler(),
1882
HTTPDefaultErrorHandler,
1885
self.open = self._opener.open
1887
# When dealing with handler order, it's easy to mess
1888
# things up, the following will help understand which
1889
# handler is used, when and for what.
1891
pprint.pprint(self._opener.__dict__)
51
1894
class HttpTransport(ConnectedTransport):