77
_ = (ssl.match_hostname, ssl.CertificateError)
78
except AttributeError:
79
# Provide fallbacks for python < 2.7.9
80
def match_hostname(cert, host):
82
'%s cannot be verified, https certificates verification is only'
83
' available for python versions >= 2.7.9' % (host,))
84
ssl.match_hostname = match_hostname
85
ssl.CertificateError = ValueError
88
# Note for packagers: if there is no package providing certs for your platform,
89
# the curl project produces http://curl.haxx.se/ca/cacert.pem weekly.
90
_ssl_ca_certs_known_locations = [
91
u'/etc/ssl/certs/ca-certificates.crt', # Ubuntu/debian/gentoo
92
u'/etc/pki/tls/certs/ca-bundle.crt', # Fedora/CentOS/RH
93
u'/etc/ssl/ca-bundle.pem', # OpenSuse
94
u'/etc/ssl/cert.pem', # OpenSuse
95
u"/usr/local/share/certs/ca-root-nss.crt", # FreeBSD
96
# XXX: Needs checking, can't trust the interweb ;) -- vila 2012-01-25
97
u'/etc/openssl/certs/ca-certificates.crt', # Solaris
101
def default_ca_certs():
102
if sys.platform == 'win32':
103
return os.path.join(os.path.dirname(sys.executable), u"cacert.pem")
104
elif sys.platform == 'darwin':
105
# FIXME: Needs some default value for osx, waiting for osx installers
106
# guys feedback -- vila 2012-01-25
109
# Try known locations for friendly OSes providing the root certificates
110
# without making them hard to use for any https client.
111
for path in _ssl_ca_certs_known_locations:
112
if os.path.exists(path):
115
# A default path that makes sense and will be mentioned in the error
116
# presented to the user, even if not correct for all platforms
117
return _ssl_ca_certs_known_locations[0]
120
def ca_certs_from_store(path):
121
if not os.path.exists(path):
122
raise ValueError("ca certs path %s does not exist" % path)
126
def cert_reqs_from_store(unicode_str):
129
return {"required": ssl.CERT_REQUIRED,
130
"none": ssl.CERT_NONE}[unicode_str]
132
raise ValueError("invalid value %s" % unicode_str)
135
def default_ca_reqs():
136
if sys.platform in ('win32', 'darwin'):
137
# FIXME: Once we get a native access to root certificates there, this
138
# won't needed anymore. See http://pad.lv/920455 -- vila 2012-02-15
143
opt_ssl_ca_certs = config.Option('ssl.ca_certs',
144
from_unicode=ca_certs_from_store,
145
default=default_ca_certs,
148
Path to certification authority certificates to trust.
150
This should be a valid path to a bundle containing all root Certificate
151
Authorities used to verify an https server certificate.
153
Use ssl.cert_reqs=none to disable certificate verification.
156
opt_ssl_cert_reqs = config.Option('ssl.cert_reqs',
157
default=default_ca_reqs,
158
from_unicode=cert_reqs_from_store,
161
Whether to require a certificate from the remote side. (default:required)
164
* none: Certificates ignored
165
* required: Certificates required and validated
168
checked_kerberos = False
172
class addinfourl(urllib2.addinfourl):
173
'''Replacement addinfourl class compatible with python-2.7's xmlrpclib
175
In python-2.7, xmlrpclib expects that the response object that it receives
176
has a getheader method. httplib.HTTPResponse provides this but
177
urllib2.addinfourl does not. Add the necessary functions here, ported to
178
use the internal data structures of addinfourl.
181
def getheader(self, name, default=None):
182
if self.headers is None:
183
raise httplib.ResponseNotReady()
184
return self.headers.getheader(name, default)
186
def getheaders(self):
187
if self.headers is None:
188
raise httplib.ResponseNotReady()
189
return self.headers.items()
78
192
class _ReportingFileSocket(object):
147
256
# Some responses have bodies in which we have no interest
148
_body_ignored_responses = [301,302, 303, 307, 401, 403, 404]
257
_body_ignored_responses = [301,302, 303, 307, 400, 401, 403, 404, 501]
150
259
# in finish() below, we may have to discard several MB in the worst
151
260
# case. To avoid buffering that much, we read and discard by chunks
246
355
def cleanup_pipe(self):
247
356
"""Read the remaining bytes of the last response if any."""
248
357
if self._response is not None:
249
pending = self._response.finish()
250
# Warn the user (once)
251
if (self._ranges_received_whole_file is None
252
and self._response.status == 200
253
and pending and pending > self._range_warning_thresold
255
self._ranges_received_whole_file = True
257
'Got a 200 response when asking for multiple ranges,'
258
' does your server at %s:%s support range requests?',
259
self.host, self.port)
359
pending = self._response.finish()
360
# Warn the user (once)
361
if (self._ranges_received_whole_file is None
362
and self._response.status == 200
363
and pending and pending > self._range_warning_thresold
365
self._ranges_received_whole_file = True
367
'Got a 200 response when asking for multiple ranges,'
368
' does your server at %s:%s support range requests?',
369
self.host, self.port)
370
except socket.error, e:
371
# It's conceivable that the socket is in a bad state here
372
# (including some test cases) and in this case, it doesn't need
373
# cleaning anymore, so no need to fail, we just get rid of the
374
# socket and let callers reconnect
376
or e.args[0] not in (errno.ECONNRESET, errno.ECONNABORTED)):
260
379
self._response = None
261
380
# Preserve our preciousss
276
395
# XXX: Needs refactoring at the caller level.
277
396
def __init__(self, host, port=None, proxied_host=None,
278
report_activity=None):
397
report_activity=None, ca_certs=None):
279
398
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
280
399
# Use strict=True since we don't support HTTP/0.9
281
400
httplib.HTTPConnection.__init__(self, host, port, strict=True)
282
401
self.proxied_host = proxied_host
402
# ca_certs is ignored, it's only relevant for https
284
404
def connect(self):
285
405
if 'http' in debug.debug_flags:
288
408
self._wrap_socket_for_reporting(self.sock)
291
# Build the appropriate socket wrapper for ssl
293
# python 2.6 introduced a better ssl package
295
_ssl_wrap_socket = ssl.wrap_socket
297
# python versions prior to 2.6 don't have ssl and ssl.wrap_socket instead
298
# they use httplib.FakeSocket
299
def _ssl_wrap_socket(sock, key_file, cert_file):
300
ssl_sock = socket.ssl(sock, key_file, cert_file)
301
return httplib.FakeSocket(sock, ssl_sock)
304
411
class HTTPSConnection(AbstractHTTPConnection, httplib.HTTPSConnection):
306
413
def __init__(self, host, port=None, key_file=None, cert_file=None,
307
414
proxied_host=None,
308
report_activity=None):
415
report_activity=None, ca_certs=None):
309
416
AbstractHTTPConnection.__init__(self, report_activity=report_activity)
310
417
# Use strict=True since we don't support HTTP/0.9
311
418
httplib.HTTPSConnection.__init__(self, host, port,
312
419
key_file, cert_file, strict=True)
313
420
self.proxied_host = proxied_host
421
self.ca_certs = ca_certs
315
423
def connect(self):
316
424
if 'http' in debug.debug_flags:
321
429
self.connect_to_origin()
323
431
def connect_to_origin(self):
324
ssl_sock = _ssl_wrap_socket(self.sock, self.key_file, self.cert_file)
432
# FIXME JRV 2011-12-18: Use location config here?
433
config_stack = config.GlobalStack()
434
cert_reqs = config_stack.get('ssl.cert_reqs')
435
if self.proxied_host is not None:
436
host = self.proxied_host.split(":", 1)[0]
439
if cert_reqs == ssl.CERT_NONE:
440
ui.ui_factory.show_user_warning('not_checking_ssl_cert', host=host)
441
ui.ui_factory.suppressed_warnings.add('not_checking_ssl_cert')
444
if self.ca_certs is None:
445
ca_certs = config_stack.get('ssl.ca_certs')
447
ca_certs = self.ca_certs
450
"No valid trusted SSL CA certificates file set. See "
451
"'bzr help ssl.ca_certs' for more information on setting "
454
ssl_sock = ssl.wrap_socket(
455
self.sock, self.key_file, self.cert_file,
456
cert_reqs=cert_reqs, ca_certs=ca_certs)
460
"See `bzr help ssl.ca_certs` for how to specify trusted CA"
462
"Pass -Ossl.cert_reqs=none to disable certificate "
463
"verification entirely.\n")
465
if cert_reqs == ssl.CERT_REQUIRED:
466
peer_cert = ssl_sock.getpeercert()
467
ssl.match_hostname(peer_cert, host)
325
469
# Wrap the ssl socket before anybody use it
326
470
self._wrap_socket_for_reporting(ssl_sock)
622
770
if 'http' in debug.debug_flags:
623
771
trace.mutter('> %s %s' % (method, url))
624
hdrs = ['%s: %s' % (k, v) for k,v in headers.items()]
773
for k,v in headers.iteritems():
774
# People are often told to paste -Dhttp output to help
775
# debug. Don't compromise credentials.
776
if k in ('Authorization', 'Proxy-Authorization'):
778
hdrs.append('%s: %s' % (k, v))
625
779
trace.mutter('> ' + '\n> '.join(hdrs) + '\n')
626
780
if self._debuglevel >= 1:
627
781
print 'Request sent: [%r] from (%s)' \
628
782
% (request, request.connection.sock.getsockname())
629
783
response = connection.getresponse()
630
784
convert_to_addinfourl = True
785
except (ssl.SSLError, ssl.CertificateError):
786
# Something is wrong with either the certificate or the hostname,
787
# re-trying won't help
631
789
except (socket.gaierror, httplib.BadStatusLine, httplib.UnknownProtocol,
632
790
socket.error, httplib.HTTPException):
633
791
response = self.retry_or_raise(http_class, request, first_try)
908
1066
def proxy_bypass(self, host):
909
"""Check if host should be proxied or not"""
1067
"""Check if host should be proxied or not.
1069
:returns: True to skip the proxy, False otherwise.
910
1071
no_proxy = self.get_proxy_env_var('no', default_to=None)
1072
bypass = self.evaluate_proxy_bypass(host, no_proxy)
1074
# Nevertheless, there are platform-specific ways to
1076
return urllib.proxy_bypass(host)
1080
def evaluate_proxy_bypass(self, host, no_proxy):
1081
"""Check the host against a comma-separated no_proxy list as a string.
1083
:param host: ``host:port`` being requested
1085
:param no_proxy: comma-separated list of hosts to access directly.
1087
:returns: True to skip the proxy, False not to, or None to
911
1090
if no_proxy is None:
1091
# All hosts are proxied
913
1093
hhost, hport = urllib.splitport(host)
914
1094
# Does host match any of the domains mentioned in
949
1130
# proxied request, intialize. scheme (the authentication scheme)
950
1131
# and realm will be set by the AuthHandler
951
1132
request.proxy_auth = {
952
'host': host, 'port': port,
953
'user': user, 'password': password,
1133
'host': parsed_url.host,
1134
'port': parsed_url.port,
1135
'user': parsed_url.user,
1136
'password': parsed_url.password,
1137
'protocol': parsed_url.scheme,
955
1138
# We ignore path since we connect to a proxy
1140
if parsed_url.port is None:
1141
phost = parsed_url.host
960
phost = host + ':%d' % port
1143
phost = parsed_url.host + ':%d' % parsed_url.port
961
1144
request.set_proxy(phost, type)
962
1145
if self._debuglevel >= 3:
963
1146
print 'set_proxy: proxy set to %s://%s' % (type, phost)
1072
1255
auth['modified'] = False
1073
1256
# Put some common info in auth if the caller didn't
1074
1257
if auth.get('path', None) is None:
1076
host, port, path) = urlutils.parse_url(request.get_full_url())
1077
self.update_auth(auth, 'protocol', protocol)
1078
self.update_auth(auth, 'host', host)
1079
self.update_auth(auth, 'port', port)
1080
self.update_auth(auth, 'path', path)
1258
parsed_url = urlutils.URL.from_string(request.get_full_url())
1259
self.update_auth(auth, 'protocol', parsed_url.scheme)
1260
self.update_auth(auth, 'host', parsed_url.host)
1261
self.update_auth(auth, 'port', parsed_url.port)
1262
self.update_auth(auth, 'path', parsed_url.path)
1081
1263
# FIXME: the auth handler should be selected at a single place instead
1082
1264
# of letting all handlers try to match all headers, but the current
1083
1265
# design doesn't allow a simple implementation.
1182
1364
user = auth.get('user', None)
1183
1365
password = auth.get('password', None)
1184
1366
realm = auth['realm']
1367
port = auth.get('port', None)
1186
1369
if user is None:
1187
1370
user = auth_conf.get_user(auth['protocol'], auth['host'],
1188
port=auth['port'], path=auth['path'],
1371
port=port, path=auth['path'],
1189
1372
realm=realm, ask=True,
1190
1373
prompt=self.build_username_prompt(auth))
1191
1374
if user is not None and password is None:
1192
1375
password = auth_conf.get_password(
1193
auth['protocol'], auth['host'], user, port=auth['port'],
1376
auth['protocol'], auth['host'], user,
1194
1378
path=auth['path'], realm=realm,
1195
1379
prompt=self.build_password_prompt(auth))