##// END OF EJS Templates
cacert: improve error report when web.cacert file does not exist
timeless -
r13544:66d65bcc stable
parent child Browse files
Show More
@@ -1,758 +1,762 b''
1 # url.py - HTTP handling for mercurial
1 # url.py - HTTP handling for mercurial
2 #
2 #
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
4 # Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
10 import urllib, urllib2, urlparse, httplib, os, re, socket, cStringIO
11 import __builtin__
11 import __builtin__
12 from i18n import _
12 from i18n import _
13 import keepalive, util
13 import keepalive, util
14
14
15 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
15 def _urlunparse(scheme, netloc, path, params, query, fragment, url):
16 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
16 '''Handle cases where urlunparse(urlparse(x://)) doesn't preserve the "//"'''
17 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
17 result = urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
18 if (scheme and
18 if (scheme and
19 result.startswith(scheme + ':') and
19 result.startswith(scheme + ':') and
20 not result.startswith(scheme + '://') and
20 not result.startswith(scheme + '://') and
21 url.startswith(scheme + '://')
21 url.startswith(scheme + '://')
22 ):
22 ):
23 result = scheme + '://' + result[len(scheme + ':'):]
23 result = scheme + '://' + result[len(scheme + ':'):]
24 return result
24 return result
25
25
26 def hidepassword(url):
26 def hidepassword(url):
27 '''hide user credential in a url string'''
27 '''hide user credential in a url string'''
28 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
28 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
29 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
29 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
30 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
30 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
31
31
32 def removeauth(url):
32 def removeauth(url):
33 '''remove all authentication information from a url string'''
33 '''remove all authentication information from a url string'''
34 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
34 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
35 netloc = netloc[netloc.find('@')+1:]
35 netloc = netloc[netloc.find('@')+1:]
36 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
36 return _urlunparse(scheme, netloc, path, params, query, fragment, url)
37
37
38 def netlocsplit(netloc):
38 def netlocsplit(netloc):
39 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
39 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
40
40
41 a = netloc.find('@')
41 a = netloc.find('@')
42 if a == -1:
42 if a == -1:
43 user, passwd = None, None
43 user, passwd = None, None
44 else:
44 else:
45 userpass, netloc = netloc[:a], netloc[a + 1:]
45 userpass, netloc = netloc[:a], netloc[a + 1:]
46 c = userpass.find(':')
46 c = userpass.find(':')
47 if c == -1:
47 if c == -1:
48 user, passwd = urllib.unquote(userpass), None
48 user, passwd = urllib.unquote(userpass), None
49 else:
49 else:
50 user = urllib.unquote(userpass[:c])
50 user = urllib.unquote(userpass[:c])
51 passwd = urllib.unquote(userpass[c + 1:])
51 passwd = urllib.unquote(userpass[c + 1:])
52 c = netloc.find(':')
52 c = netloc.find(':')
53 if c == -1:
53 if c == -1:
54 host, port = netloc, None
54 host, port = netloc, None
55 else:
55 else:
56 host, port = netloc[:c], netloc[c + 1:]
56 host, port = netloc[:c], netloc[c + 1:]
57 return host, port, user, passwd
57 return host, port, user, passwd
58
58
59 def netlocunsplit(host, port, user=None, passwd=None):
59 def netlocunsplit(host, port, user=None, passwd=None):
60 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
60 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
61 if port:
61 if port:
62 hostport = host + ':' + port
62 hostport = host + ':' + port
63 else:
63 else:
64 hostport = host
64 hostport = host
65 if user:
65 if user:
66 quote = lambda s: urllib.quote(s, safe='')
66 quote = lambda s: urllib.quote(s, safe='')
67 if passwd:
67 if passwd:
68 userpass = quote(user) + ':' + quote(passwd)
68 userpass = quote(user) + ':' + quote(passwd)
69 else:
69 else:
70 userpass = quote(user)
70 userpass = quote(user)
71 return userpass + '@' + hostport
71 return userpass + '@' + hostport
72 return hostport
72 return hostport
73
73
74 def readauthforuri(ui, uri):
74 def readauthforuri(ui, uri):
75 # Read configuration
75 # Read configuration
76 config = dict()
76 config = dict()
77 for key, val in ui.configitems('auth'):
77 for key, val in ui.configitems('auth'):
78 if '.' not in key:
78 if '.' not in key:
79 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
79 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
80 continue
80 continue
81 group, setting = key.rsplit('.', 1)
81 group, setting = key.rsplit('.', 1)
82 gdict = config.setdefault(group, dict())
82 gdict = config.setdefault(group, dict())
83 if setting in ('username', 'cert', 'key'):
83 if setting in ('username', 'cert', 'key'):
84 val = util.expandpath(val)
84 val = util.expandpath(val)
85 gdict[setting] = val
85 gdict[setting] = val
86
86
87 # Find the best match
87 # Find the best match
88 scheme, hostpath = uri.split('://', 1)
88 scheme, hostpath = uri.split('://', 1)
89 bestlen = 0
89 bestlen = 0
90 bestauth = None
90 bestauth = None
91 for group, auth in config.iteritems():
91 for group, auth in config.iteritems():
92 prefix = auth.get('prefix')
92 prefix = auth.get('prefix')
93 if not prefix:
93 if not prefix:
94 continue
94 continue
95 p = prefix.split('://', 1)
95 p = prefix.split('://', 1)
96 if len(p) > 1:
96 if len(p) > 1:
97 schemes, prefix = [p[0]], p[1]
97 schemes, prefix = [p[0]], p[1]
98 else:
98 else:
99 schemes = (auth.get('schemes') or 'https').split()
99 schemes = (auth.get('schemes') or 'https').split()
100 if (prefix == '*' or hostpath.startswith(prefix)) and \
100 if (prefix == '*' or hostpath.startswith(prefix)) and \
101 len(prefix) > bestlen and scheme in schemes:
101 len(prefix) > bestlen and scheme in schemes:
102 bestlen = len(prefix)
102 bestlen = len(prefix)
103 bestauth = group, auth
103 bestauth = group, auth
104 return bestauth
104 return bestauth
105
105
106 _safe = ('abcdefghijklmnopqrstuvwxyz'
106 _safe = ('abcdefghijklmnopqrstuvwxyz'
107 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
107 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
108 '0123456789' '_.-/')
108 '0123456789' '_.-/')
109 _safeset = None
109 _safeset = None
110 _hex = None
110 _hex = None
111 def quotepath(path):
111 def quotepath(path):
112 '''quote the path part of a URL
112 '''quote the path part of a URL
113
113
114 This is similar to urllib.quote, but it also tries to avoid
114 This is similar to urllib.quote, but it also tries to avoid
115 quoting things twice (inspired by wget):
115 quoting things twice (inspired by wget):
116
116
117 >>> quotepath('abc def')
117 >>> quotepath('abc def')
118 'abc%20def'
118 'abc%20def'
119 >>> quotepath('abc%20def')
119 >>> quotepath('abc%20def')
120 'abc%20def'
120 'abc%20def'
121 >>> quotepath('abc%20 def')
121 >>> quotepath('abc%20 def')
122 'abc%20%20def'
122 'abc%20%20def'
123 >>> quotepath('abc def%20')
123 >>> quotepath('abc def%20')
124 'abc%20def%20'
124 'abc%20def%20'
125 >>> quotepath('abc def%2')
125 >>> quotepath('abc def%2')
126 'abc%20def%252'
126 'abc%20def%252'
127 >>> quotepath('abc def%')
127 >>> quotepath('abc def%')
128 'abc%20def%25'
128 'abc%20def%25'
129 '''
129 '''
130 global _safeset, _hex
130 global _safeset, _hex
131 if _safeset is None:
131 if _safeset is None:
132 _safeset = set(_safe)
132 _safeset = set(_safe)
133 _hex = set('abcdefABCDEF0123456789')
133 _hex = set('abcdefABCDEF0123456789')
134 l = list(path)
134 l = list(path)
135 for i in xrange(len(l)):
135 for i in xrange(len(l)):
136 c = l[i]
136 c = l[i]
137 if (c == '%' and i + 2 < len(l) and
137 if (c == '%' and i + 2 < len(l) and
138 l[i + 1] in _hex and l[i + 2] in _hex):
138 l[i + 1] in _hex and l[i + 2] in _hex):
139 pass
139 pass
140 elif c not in _safeset:
140 elif c not in _safeset:
141 l[i] = '%%%02X' % ord(c)
141 l[i] = '%%%02X' % ord(c)
142 return ''.join(l)
142 return ''.join(l)
143
143
144 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
144 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
145 def __init__(self, ui):
145 def __init__(self, ui):
146 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
146 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
147 self.ui = ui
147 self.ui = ui
148
148
149 def find_user_password(self, realm, authuri):
149 def find_user_password(self, realm, authuri):
150 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
150 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
151 self, realm, authuri)
151 self, realm, authuri)
152 user, passwd = authinfo
152 user, passwd = authinfo
153 if user and passwd:
153 if user and passwd:
154 self._writedebug(user, passwd)
154 self._writedebug(user, passwd)
155 return (user, passwd)
155 return (user, passwd)
156
156
157 if not user:
157 if not user:
158 res = readauthforuri(self.ui, authuri)
158 res = readauthforuri(self.ui, authuri)
159 if res:
159 if res:
160 group, auth = res
160 group, auth = res
161 user, passwd = auth.get('username'), auth.get('password')
161 user, passwd = auth.get('username'), auth.get('password')
162 self.ui.debug("using auth.%s.* for authentication\n" % group)
162 self.ui.debug("using auth.%s.* for authentication\n" % group)
163 if not user or not passwd:
163 if not user or not passwd:
164 if not self.ui.interactive():
164 if not self.ui.interactive():
165 raise util.Abort(_('http authorization required'))
165 raise util.Abort(_('http authorization required'))
166
166
167 self.ui.write(_("http authorization required\n"))
167 self.ui.write(_("http authorization required\n"))
168 self.ui.write(_("realm: %s\n") % realm)
168 self.ui.write(_("realm: %s\n") % realm)
169 if user:
169 if user:
170 self.ui.write(_("user: %s\n") % user)
170 self.ui.write(_("user: %s\n") % user)
171 else:
171 else:
172 user = self.ui.prompt(_("user:"), default=None)
172 user = self.ui.prompt(_("user:"), default=None)
173
173
174 if not passwd:
174 if not passwd:
175 passwd = self.ui.getpass()
175 passwd = self.ui.getpass()
176
176
177 self.add_password(realm, authuri, user, passwd)
177 self.add_password(realm, authuri, user, passwd)
178 self._writedebug(user, passwd)
178 self._writedebug(user, passwd)
179 return (user, passwd)
179 return (user, passwd)
180
180
181 def _writedebug(self, user, passwd):
181 def _writedebug(self, user, passwd):
182 msg = _('http auth: user %s, password %s\n')
182 msg = _('http auth: user %s, password %s\n')
183 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
183 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
184
184
185 class proxyhandler(urllib2.ProxyHandler):
185 class proxyhandler(urllib2.ProxyHandler):
186 def __init__(self, ui):
186 def __init__(self, ui):
187 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
187 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
188 # XXX proxyauthinfo = None
188 # XXX proxyauthinfo = None
189
189
190 if proxyurl:
190 if proxyurl:
191 # proxy can be proper url or host[:port]
191 # proxy can be proper url or host[:port]
192 if not (proxyurl.startswith('http:') or
192 if not (proxyurl.startswith('http:') or
193 proxyurl.startswith('https:')):
193 proxyurl.startswith('https:')):
194 proxyurl = 'http://' + proxyurl + '/'
194 proxyurl = 'http://' + proxyurl + '/'
195 snpqf = urlparse.urlsplit(proxyurl)
195 snpqf = urlparse.urlsplit(proxyurl)
196 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
196 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
197 hpup = netlocsplit(proxynetloc)
197 hpup = netlocsplit(proxynetloc)
198
198
199 proxyhost, proxyport, proxyuser, proxypasswd = hpup
199 proxyhost, proxyport, proxyuser, proxypasswd = hpup
200 if not proxyuser:
200 if not proxyuser:
201 proxyuser = ui.config("http_proxy", "user")
201 proxyuser = ui.config("http_proxy", "user")
202 proxypasswd = ui.config("http_proxy", "passwd")
202 proxypasswd = ui.config("http_proxy", "passwd")
203
203
204 # see if we should use a proxy for this url
204 # see if we should use a proxy for this url
205 no_list = ["localhost", "127.0.0.1"]
205 no_list = ["localhost", "127.0.0.1"]
206 no_list.extend([p.lower() for
206 no_list.extend([p.lower() for
207 p in ui.configlist("http_proxy", "no")])
207 p in ui.configlist("http_proxy", "no")])
208 no_list.extend([p.strip().lower() for
208 no_list.extend([p.strip().lower() for
209 p in os.getenv("no_proxy", '').split(',')
209 p in os.getenv("no_proxy", '').split(',')
210 if p.strip()])
210 if p.strip()])
211 # "http_proxy.always" config is for running tests on localhost
211 # "http_proxy.always" config is for running tests on localhost
212 if ui.configbool("http_proxy", "always"):
212 if ui.configbool("http_proxy", "always"):
213 self.no_list = []
213 self.no_list = []
214 else:
214 else:
215 self.no_list = no_list
215 self.no_list = no_list
216
216
217 proxyurl = urlparse.urlunsplit((
217 proxyurl = urlparse.urlunsplit((
218 proxyscheme, netlocunsplit(proxyhost, proxyport,
218 proxyscheme, netlocunsplit(proxyhost, proxyport,
219 proxyuser, proxypasswd or ''),
219 proxyuser, proxypasswd or ''),
220 proxypath, proxyquery, proxyfrag))
220 proxypath, proxyquery, proxyfrag))
221 proxies = {'http': proxyurl, 'https': proxyurl}
221 proxies = {'http': proxyurl, 'https': proxyurl}
222 ui.debug('proxying through http://%s:%s\n' %
222 ui.debug('proxying through http://%s:%s\n' %
223 (proxyhost, proxyport))
223 (proxyhost, proxyport))
224 else:
224 else:
225 proxies = {}
225 proxies = {}
226
226
227 # urllib2 takes proxy values from the environment and those
227 # urllib2 takes proxy values from the environment and those
228 # will take precedence if found, so drop them
228 # will take precedence if found, so drop them
229 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
229 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
230 try:
230 try:
231 if env in os.environ:
231 if env in os.environ:
232 del os.environ[env]
232 del os.environ[env]
233 except OSError:
233 except OSError:
234 pass
234 pass
235
235
236 urllib2.ProxyHandler.__init__(self, proxies)
236 urllib2.ProxyHandler.__init__(self, proxies)
237 self.ui = ui
237 self.ui = ui
238
238
239 def proxy_open(self, req, proxy, type_):
239 def proxy_open(self, req, proxy, type_):
240 host = req.get_host().split(':')[0]
240 host = req.get_host().split(':')[0]
241 if host in self.no_list:
241 if host in self.no_list:
242 return None
242 return None
243
243
244 # work around a bug in Python < 2.4.2
244 # work around a bug in Python < 2.4.2
245 # (it leaves a "\n" at the end of Proxy-authorization headers)
245 # (it leaves a "\n" at the end of Proxy-authorization headers)
246 baseclass = req.__class__
246 baseclass = req.__class__
247 class _request(baseclass):
247 class _request(baseclass):
248 def add_header(self, key, val):
248 def add_header(self, key, val):
249 if key.lower() == 'proxy-authorization':
249 if key.lower() == 'proxy-authorization':
250 val = val.strip()
250 val = val.strip()
251 return baseclass.add_header(self, key, val)
251 return baseclass.add_header(self, key, val)
252 req.__class__ = _request
252 req.__class__ = _request
253
253
254 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
254 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
255
255
256 class httpsendfile(object):
256 class httpsendfile(object):
257 """This is a wrapper around the objects returned by python's "open".
257 """This is a wrapper around the objects returned by python's "open".
258
258
259 Its purpose is to send file-like objects via HTTP and, to do so, it
259 Its purpose is to send file-like objects via HTTP and, to do so, it
260 defines a __len__ attribute to feed the Content-Length header.
260 defines a __len__ attribute to feed the Content-Length header.
261 """
261 """
262
262
263 def __init__(self, ui, *args, **kwargs):
263 def __init__(self, ui, *args, **kwargs):
264 # We can't just "self._data = open(*args, **kwargs)" here because there
264 # We can't just "self._data = open(*args, **kwargs)" here because there
265 # is an "open" function defined in this module that shadows the global
265 # is an "open" function defined in this module that shadows the global
266 # one
266 # one
267 self.ui = ui
267 self.ui = ui
268 self._data = __builtin__.open(*args, **kwargs)
268 self._data = __builtin__.open(*args, **kwargs)
269 self.seek = self._data.seek
269 self.seek = self._data.seek
270 self.close = self._data.close
270 self.close = self._data.close
271 self.write = self._data.write
271 self.write = self._data.write
272 self._len = os.fstat(self._data.fileno()).st_size
272 self._len = os.fstat(self._data.fileno()).st_size
273 self._pos = 0
273 self._pos = 0
274 self._total = len(self) / 1024 * 2
274 self._total = len(self) / 1024 * 2
275
275
276 def read(self, *args, **kwargs):
276 def read(self, *args, **kwargs):
277 try:
277 try:
278 ret = self._data.read(*args, **kwargs)
278 ret = self._data.read(*args, **kwargs)
279 except EOFError:
279 except EOFError:
280 self.ui.progress(_('sending'), None)
280 self.ui.progress(_('sending'), None)
281 self._pos += len(ret)
281 self._pos += len(ret)
282 # We pass double the max for total because we currently have
282 # We pass double the max for total because we currently have
283 # to send the bundle twice in the case of a server that
283 # to send the bundle twice in the case of a server that
284 # requires authentication. Since we can't know until we try
284 # requires authentication. Since we can't know until we try
285 # once whether authentication will be required, just lie to
285 # once whether authentication will be required, just lie to
286 # the user and maybe the push succeeds suddenly at 50%.
286 # the user and maybe the push succeeds suddenly at 50%.
287 self.ui.progress(_('sending'), self._pos / 1024,
287 self.ui.progress(_('sending'), self._pos / 1024,
288 unit=_('kb'), total=self._total)
288 unit=_('kb'), total=self._total)
289 return ret
289 return ret
290
290
291 def __len__(self):
291 def __len__(self):
292 return self._len
292 return self._len
293
293
294 def _gen_sendfile(orgsend):
294 def _gen_sendfile(orgsend):
295 def _sendfile(self, data):
295 def _sendfile(self, data):
296 # send a file
296 # send a file
297 if isinstance(data, httpsendfile):
297 if isinstance(data, httpsendfile):
298 # if auth required, some data sent twice, so rewind here
298 # if auth required, some data sent twice, so rewind here
299 data.seek(0)
299 data.seek(0)
300 for chunk in util.filechunkiter(data):
300 for chunk in util.filechunkiter(data):
301 orgsend(self, chunk)
301 orgsend(self, chunk)
302 else:
302 else:
303 orgsend(self, data)
303 orgsend(self, data)
304 return _sendfile
304 return _sendfile
305
305
306 has_https = hasattr(urllib2, 'HTTPSHandler')
306 has_https = hasattr(urllib2, 'HTTPSHandler')
307 if has_https:
307 if has_https:
308 try:
308 try:
309 # avoid using deprecated/broken FakeSocket in python 2.6
309 # avoid using deprecated/broken FakeSocket in python 2.6
310 import ssl
310 import ssl
311 _ssl_wrap_socket = ssl.wrap_socket
311 _ssl_wrap_socket = ssl.wrap_socket
312 CERT_REQUIRED = ssl.CERT_REQUIRED
312 CERT_REQUIRED = ssl.CERT_REQUIRED
313 except ImportError:
313 except ImportError:
314 CERT_REQUIRED = 2
314 CERT_REQUIRED = 2
315
315
316 def _ssl_wrap_socket(sock, key_file, cert_file,
316 def _ssl_wrap_socket(sock, key_file, cert_file,
317 cert_reqs=CERT_REQUIRED, ca_certs=None):
317 cert_reqs=CERT_REQUIRED, ca_certs=None):
318 if ca_certs:
318 if ca_certs:
319 raise util.Abort(_(
319 raise util.Abort(_(
320 'certificate checking requires Python 2.6'))
320 'certificate checking requires Python 2.6'))
321
321
322 ssl = socket.ssl(sock, key_file, cert_file)
322 ssl = socket.ssl(sock, key_file, cert_file)
323 return httplib.FakeSocket(sock, ssl)
323 return httplib.FakeSocket(sock, ssl)
324
324
325 try:
325 try:
326 _create_connection = socket.create_connection
326 _create_connection = socket.create_connection
327 except AttributeError:
327 except AttributeError:
328 _GLOBAL_DEFAULT_TIMEOUT = object()
328 _GLOBAL_DEFAULT_TIMEOUT = object()
329
329
330 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
330 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
331 source_address=None):
331 source_address=None):
332 # lifted from Python 2.6
332 # lifted from Python 2.6
333
333
334 msg = "getaddrinfo returns an empty list"
334 msg = "getaddrinfo returns an empty list"
335 host, port = address
335 host, port = address
336 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
336 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
337 af, socktype, proto, canonname, sa = res
337 af, socktype, proto, canonname, sa = res
338 sock = None
338 sock = None
339 try:
339 try:
340 sock = socket.socket(af, socktype, proto)
340 sock = socket.socket(af, socktype, proto)
341 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
341 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
342 sock.settimeout(timeout)
342 sock.settimeout(timeout)
343 if source_address:
343 if source_address:
344 sock.bind(source_address)
344 sock.bind(source_address)
345 sock.connect(sa)
345 sock.connect(sa)
346 return sock
346 return sock
347
347
348 except socket.error, msg:
348 except socket.error, msg:
349 if sock is not None:
349 if sock is not None:
350 sock.close()
350 sock.close()
351
351
352 raise socket.error, msg
352 raise socket.error, msg
353
353
354 class httpconnection(keepalive.HTTPConnection):
354 class httpconnection(keepalive.HTTPConnection):
355 # must be able to send big bundle as stream.
355 # must be able to send big bundle as stream.
356 send = _gen_sendfile(keepalive.HTTPConnection.send)
356 send = _gen_sendfile(keepalive.HTTPConnection.send)
357
357
358 def connect(self):
358 def connect(self):
359 if has_https and self.realhostport: # use CONNECT proxy
359 if has_https and self.realhostport: # use CONNECT proxy
360 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
360 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
361 self.sock.connect((self.host, self.port))
361 self.sock.connect((self.host, self.port))
362 if _generic_proxytunnel(self):
362 if _generic_proxytunnel(self):
363 # we do not support client x509 certificates
363 # we do not support client x509 certificates
364 self.sock = _ssl_wrap_socket(self.sock, None, None)
364 self.sock = _ssl_wrap_socket(self.sock, None, None)
365 else:
365 else:
366 keepalive.HTTPConnection.connect(self)
366 keepalive.HTTPConnection.connect(self)
367
367
368 def getresponse(self):
368 def getresponse(self):
369 proxyres = getattr(self, 'proxyres', None)
369 proxyres = getattr(self, 'proxyres', None)
370 if proxyres:
370 if proxyres:
371 if proxyres.will_close:
371 if proxyres.will_close:
372 self.close()
372 self.close()
373 self.proxyres = None
373 self.proxyres = None
374 return proxyres
374 return proxyres
375 return keepalive.HTTPConnection.getresponse(self)
375 return keepalive.HTTPConnection.getresponse(self)
376
376
377 # general transaction handler to support different ways to handle
377 # general transaction handler to support different ways to handle
378 # HTTPS proxying before and after Python 2.6.3.
378 # HTTPS proxying before and after Python 2.6.3.
379 def _generic_start_transaction(handler, h, req):
379 def _generic_start_transaction(handler, h, req):
380 if hasattr(req, '_tunnel_host') and req._tunnel_host:
380 if hasattr(req, '_tunnel_host') and req._tunnel_host:
381 tunnel_host = req._tunnel_host
381 tunnel_host = req._tunnel_host
382 if tunnel_host[:7] not in ['http://', 'https:/']:
382 if tunnel_host[:7] not in ['http://', 'https:/']:
383 tunnel_host = 'https://' + tunnel_host
383 tunnel_host = 'https://' + tunnel_host
384 new_tunnel = True
384 new_tunnel = True
385 else:
385 else:
386 tunnel_host = req.get_selector()
386 tunnel_host = req.get_selector()
387 new_tunnel = False
387 new_tunnel = False
388
388
389 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
389 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
390 urlparts = urlparse.urlparse(tunnel_host)
390 urlparts = urlparse.urlparse(tunnel_host)
391 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
391 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
392 realhostport = urlparts[1]
392 realhostport = urlparts[1]
393 if realhostport[-1] == ']' or ':' not in realhostport:
393 if realhostport[-1] == ']' or ':' not in realhostport:
394 realhostport += ':443'
394 realhostport += ':443'
395
395
396 h.realhostport = realhostport
396 h.realhostport = realhostport
397 h.headers = req.headers.copy()
397 h.headers = req.headers.copy()
398 h.headers.update(handler.parent.addheaders)
398 h.headers.update(handler.parent.addheaders)
399 return
399 return
400
400
401 h.realhostport = None
401 h.realhostport = None
402 h.headers = None
402 h.headers = None
403
403
404 def _generic_proxytunnel(self):
404 def _generic_proxytunnel(self):
405 proxyheaders = dict(
405 proxyheaders = dict(
406 [(x, self.headers[x]) for x in self.headers
406 [(x, self.headers[x]) for x in self.headers
407 if x.lower().startswith('proxy-')])
407 if x.lower().startswith('proxy-')])
408 self._set_hostport(self.host, self.port)
408 self._set_hostport(self.host, self.port)
409 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
409 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
410 for header in proxyheaders.iteritems():
410 for header in proxyheaders.iteritems():
411 self.send('%s: %s\r\n' % header)
411 self.send('%s: %s\r\n' % header)
412 self.send('\r\n')
412 self.send('\r\n')
413
413
414 # majority of the following code is duplicated from
414 # majority of the following code is duplicated from
415 # httplib.HTTPConnection as there are no adequate places to
415 # httplib.HTTPConnection as there are no adequate places to
416 # override functions to provide the needed functionality
416 # override functions to provide the needed functionality
417 res = self.response_class(self.sock,
417 res = self.response_class(self.sock,
418 strict=self.strict,
418 strict=self.strict,
419 method=self._method)
419 method=self._method)
420
420
421 while True:
421 while True:
422 version, status, reason = res._read_status()
422 version, status, reason = res._read_status()
423 if status != httplib.CONTINUE:
423 if status != httplib.CONTINUE:
424 break
424 break
425 while True:
425 while True:
426 skip = res.fp.readline().strip()
426 skip = res.fp.readline().strip()
427 if not skip:
427 if not skip:
428 break
428 break
429 res.status = status
429 res.status = status
430 res.reason = reason.strip()
430 res.reason = reason.strip()
431
431
432 if res.status == 200:
432 if res.status == 200:
433 while True:
433 while True:
434 line = res.fp.readline()
434 line = res.fp.readline()
435 if line == '\r\n':
435 if line == '\r\n':
436 break
436 break
437 return True
437 return True
438
438
439 if version == 'HTTP/1.0':
439 if version == 'HTTP/1.0':
440 res.version = 10
440 res.version = 10
441 elif version.startswith('HTTP/1.'):
441 elif version.startswith('HTTP/1.'):
442 res.version = 11
442 res.version = 11
443 elif version == 'HTTP/0.9':
443 elif version == 'HTTP/0.9':
444 res.version = 9
444 res.version = 9
445 else:
445 else:
446 raise httplib.UnknownProtocol(version)
446 raise httplib.UnknownProtocol(version)
447
447
448 if res.version == 9:
448 if res.version == 9:
449 res.length = None
449 res.length = None
450 res.chunked = 0
450 res.chunked = 0
451 res.will_close = 1
451 res.will_close = 1
452 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
452 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
453 return False
453 return False
454
454
455 res.msg = httplib.HTTPMessage(res.fp)
455 res.msg = httplib.HTTPMessage(res.fp)
456 res.msg.fp = None
456 res.msg.fp = None
457
457
458 # are we using the chunked-style of transfer encoding?
458 # are we using the chunked-style of transfer encoding?
459 trenc = res.msg.getheader('transfer-encoding')
459 trenc = res.msg.getheader('transfer-encoding')
460 if trenc and trenc.lower() == "chunked":
460 if trenc and trenc.lower() == "chunked":
461 res.chunked = 1
461 res.chunked = 1
462 res.chunk_left = None
462 res.chunk_left = None
463 else:
463 else:
464 res.chunked = 0
464 res.chunked = 0
465
465
466 # will the connection close at the end of the response?
466 # will the connection close at the end of the response?
467 res.will_close = res._check_close()
467 res.will_close = res._check_close()
468
468
469 # do we have a Content-Length?
469 # do we have a Content-Length?
470 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
470 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
471 length = res.msg.getheader('content-length')
471 length = res.msg.getheader('content-length')
472 if length and not res.chunked:
472 if length and not res.chunked:
473 try:
473 try:
474 res.length = int(length)
474 res.length = int(length)
475 except ValueError:
475 except ValueError:
476 res.length = None
476 res.length = None
477 else:
477 else:
478 if res.length < 0: # ignore nonsensical negative lengths
478 if res.length < 0: # ignore nonsensical negative lengths
479 res.length = None
479 res.length = None
480 else:
480 else:
481 res.length = None
481 res.length = None
482
482
483 # does the body have a fixed length? (of zero)
483 # does the body have a fixed length? (of zero)
484 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
484 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
485 100 <= status < 200 or # 1xx codes
485 100 <= status < 200 or # 1xx codes
486 res._method == 'HEAD'):
486 res._method == 'HEAD'):
487 res.length = 0
487 res.length = 0
488
488
489 # if the connection remains open, and we aren't using chunked, and
489 # if the connection remains open, and we aren't using chunked, and
490 # a content-length was not provided, then assume that the connection
490 # a content-length was not provided, then assume that the connection
491 # WILL close.
491 # WILL close.
492 if (not res.will_close and
492 if (not res.will_close and
493 not res.chunked and
493 not res.chunked and
494 res.length is None):
494 res.length is None):
495 res.will_close = 1
495 res.will_close = 1
496
496
497 self.proxyres = res
497 self.proxyres = res
498
498
499 return False
499 return False
500
500
501 class httphandler(keepalive.HTTPHandler):
501 class httphandler(keepalive.HTTPHandler):
502 def http_open(self, req):
502 def http_open(self, req):
503 return self.do_open(httpconnection, req)
503 return self.do_open(httpconnection, req)
504
504
505 def _start_transaction(self, h, req):
505 def _start_transaction(self, h, req):
506 _generic_start_transaction(self, h, req)
506 _generic_start_transaction(self, h, req)
507 return keepalive.HTTPHandler._start_transaction(self, h, req)
507 return keepalive.HTTPHandler._start_transaction(self, h, req)
508
508
509 def _verifycert(cert, hostname):
509 def _verifycert(cert, hostname):
510 '''Verify that cert (in socket.getpeercert() format) matches hostname.
510 '''Verify that cert (in socket.getpeercert() format) matches hostname.
511 CRLs is not handled.
511 CRLs is not handled.
512
512
513 Returns error message if any problems are found and None on success.
513 Returns error message if any problems are found and None on success.
514 '''
514 '''
515 if not cert:
515 if not cert:
516 return _('no certificate received')
516 return _('no certificate received')
517 dnsname = hostname.lower()
517 dnsname = hostname.lower()
518 def matchdnsname(certname):
518 def matchdnsname(certname):
519 return (certname == dnsname or
519 return (certname == dnsname or
520 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
520 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
521
521
522 san = cert.get('subjectAltName', [])
522 san = cert.get('subjectAltName', [])
523 if san:
523 if san:
524 certnames = [value.lower() for key, value in san if key == 'DNS']
524 certnames = [value.lower() for key, value in san if key == 'DNS']
525 for name in certnames:
525 for name in certnames:
526 if matchdnsname(name):
526 if matchdnsname(name):
527 return None
527 return None
528 return _('certificate is for %s') % ', '.join(certnames)
528 return _('certificate is for %s') % ', '.join(certnames)
529
529
530 # subject is only checked when subjectAltName is empty
530 # subject is only checked when subjectAltName is empty
531 for s in cert.get('subject', []):
531 for s in cert.get('subject', []):
532 key, value = s[0]
532 key, value = s[0]
533 if key == 'commonName':
533 if key == 'commonName':
534 try:
534 try:
535 # 'subject' entries are unicode
535 # 'subject' entries are unicode
536 certname = value.lower().encode('ascii')
536 certname = value.lower().encode('ascii')
537 except UnicodeEncodeError:
537 except UnicodeEncodeError:
538 return _('IDN in certificate not supported')
538 return _('IDN in certificate not supported')
539 if matchdnsname(certname):
539 if matchdnsname(certname):
540 return None
540 return None
541 return _('certificate is for %s') % certname
541 return _('certificate is for %s') % certname
542 return _('no commonName or subjectAltName found in certificate')
542 return _('no commonName or subjectAltName found in certificate')
543
543
544 if has_https:
544 if has_https:
545 class httpsconnection(httplib.HTTPSConnection):
545 class httpsconnection(httplib.HTTPSConnection):
546 response_class = keepalive.HTTPResponse
546 response_class = keepalive.HTTPResponse
547 # must be able to send big bundle as stream.
547 # must be able to send big bundle as stream.
548 send = _gen_sendfile(keepalive.safesend)
548 send = _gen_sendfile(keepalive.safesend)
549 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
549 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
550
550
551 def connect(self):
551 def connect(self):
552 self.sock = _create_connection((self.host, self.port))
552 self.sock = _create_connection((self.host, self.port))
553
553
554 host = self.host
554 host = self.host
555 if self.realhostport: # use CONNECT proxy
555 if self.realhostport: # use CONNECT proxy
556 something = _generic_proxytunnel(self)
556 something = _generic_proxytunnel(self)
557 host = self.realhostport.rsplit(':', 1)[0]
557 host = self.realhostport.rsplit(':', 1)[0]
558
558
559 cacerts = self.ui.config('web', 'cacerts')
559 cacerts = self.ui.config('web', 'cacerts')
560 hostfingerprint = self.ui.config('hostfingerprints', host)
560 hostfingerprint = self.ui.config('hostfingerprints', host)
561
561
562 if cacerts and not hostfingerprint:
562 if cacerts and not hostfingerprint:
563 cacerts = util.expandpath(cacerts)
564 if not os.path.exists(cacerts):
565 raise util.Abort(_('could not find '
566 'web.cacerts: %s') % cacerts)
563 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
567 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
564 self.cert_file, cert_reqs=CERT_REQUIRED,
568 self.cert_file, cert_reqs=CERT_REQUIRED,
565 ca_certs=util.expandpath(cacerts))
569 ca_certs=cacerts)
566 msg = _verifycert(self.sock.getpeercert(), host)
570 msg = _verifycert(self.sock.getpeercert(), host)
567 if msg:
571 if msg:
568 raise util.Abort(_('%s certificate error: %s '
572 raise util.Abort(_('%s certificate error: %s '
569 '(use --insecure to connect '
573 '(use --insecure to connect '
570 'insecurely)') % (host, msg))
574 'insecurely)') % (host, msg))
571 self.ui.debug('%s certificate successfully verified\n' % host)
575 self.ui.debug('%s certificate successfully verified\n' % host)
572 else:
576 else:
573 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
577 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
574 self.cert_file)
578 self.cert_file)
575 if hasattr(self.sock, 'getpeercert'):
579 if hasattr(self.sock, 'getpeercert'):
576 peercert = self.sock.getpeercert(True)
580 peercert = self.sock.getpeercert(True)
577 peerfingerprint = util.sha1(peercert).hexdigest()
581 peerfingerprint = util.sha1(peercert).hexdigest()
578 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
582 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
579 for x in xrange(0, len(peerfingerprint), 2)])
583 for x in xrange(0, len(peerfingerprint), 2)])
580 if hostfingerprint:
584 if hostfingerprint:
581 if peerfingerprint.lower() != \
585 if peerfingerprint.lower() != \
582 hostfingerprint.replace(':', '').lower():
586 hostfingerprint.replace(':', '').lower():
583 raise util.Abort(_('invalid certificate for %s '
587 raise util.Abort(_('invalid certificate for %s '
584 'with fingerprint %s') %
588 'with fingerprint %s') %
585 (host, nicefingerprint))
589 (host, nicefingerprint))
586 self.ui.debug('%s certificate matched fingerprint %s\n' %
590 self.ui.debug('%s certificate matched fingerprint %s\n' %
587 (host, nicefingerprint))
591 (host, nicefingerprint))
588 else:
592 else:
589 self.ui.warn(_('warning: %s certificate '
593 self.ui.warn(_('warning: %s certificate '
590 'with fingerprint %s not verified '
594 'with fingerprint %s not verified '
591 '(check hostfingerprints or web.cacerts '
595 '(check hostfingerprints or web.cacerts '
592 'config setting)\n') %
596 'config setting)\n') %
593 (host, nicefingerprint))
597 (host, nicefingerprint))
594 else: # python 2.5 ?
598 else: # python 2.5 ?
595 if hostfingerprint:
599 if hostfingerprint:
596 raise util.Abort(_('no certificate for %s with '
600 raise util.Abort(_('no certificate for %s with '
597 'configured hostfingerprint') % host)
601 'configured hostfingerprint') % host)
598 self.ui.warn(_('warning: %s certificate not verified '
602 self.ui.warn(_('warning: %s certificate not verified '
599 '(check web.cacerts config setting)\n') %
603 '(check web.cacerts config setting)\n') %
600 host)
604 host)
601
605
602 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
606 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
603 def __init__(self, ui):
607 def __init__(self, ui):
604 keepalive.KeepAliveHandler.__init__(self)
608 keepalive.KeepAliveHandler.__init__(self)
605 urllib2.HTTPSHandler.__init__(self)
609 urllib2.HTTPSHandler.__init__(self)
606 self.ui = ui
610 self.ui = ui
607 self.pwmgr = passwordmgr(self.ui)
611 self.pwmgr = passwordmgr(self.ui)
608
612
609 def _start_transaction(self, h, req):
613 def _start_transaction(self, h, req):
610 _generic_start_transaction(self, h, req)
614 _generic_start_transaction(self, h, req)
611 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
615 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
612
616
613 def https_open(self, req):
617 def https_open(self, req):
614 res = readauthforuri(self.ui, req.get_full_url())
618 res = readauthforuri(self.ui, req.get_full_url())
615 if res:
619 if res:
616 group, auth = res
620 group, auth = res
617 self.auth = auth
621 self.auth = auth
618 self.ui.debug("using auth.%s.* for authentication\n" % group)
622 self.ui.debug("using auth.%s.* for authentication\n" % group)
619 else:
623 else:
620 self.auth = None
624 self.auth = None
621 return self.do_open(self._makeconnection, req)
625 return self.do_open(self._makeconnection, req)
622
626
623 def _makeconnection(self, host, port=None, *args, **kwargs):
627 def _makeconnection(self, host, port=None, *args, **kwargs):
624 keyfile = None
628 keyfile = None
625 certfile = None
629 certfile = None
626
630
627 if len(args) >= 1: # key_file
631 if len(args) >= 1: # key_file
628 keyfile = args[0]
632 keyfile = args[0]
629 if len(args) >= 2: # cert_file
633 if len(args) >= 2: # cert_file
630 certfile = args[1]
634 certfile = args[1]
631 args = args[2:]
635 args = args[2:]
632
636
633 # if the user has specified different key/cert files in
637 # if the user has specified different key/cert files in
634 # hgrc, we prefer these
638 # hgrc, we prefer these
635 if self.auth and 'key' in self.auth and 'cert' in self.auth:
639 if self.auth and 'key' in self.auth and 'cert' in self.auth:
636 keyfile = self.auth['key']
640 keyfile = self.auth['key']
637 certfile = self.auth['cert']
641 certfile = self.auth['cert']
638
642
639 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
643 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
640 conn.ui = self.ui
644 conn.ui = self.ui
641 return conn
645 return conn
642
646
643 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
647 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
644 def __init__(self, *args, **kwargs):
648 def __init__(self, *args, **kwargs):
645 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
649 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
646 self.retried_req = None
650 self.retried_req = None
647
651
648 def reset_retry_count(self):
652 def reset_retry_count(self):
649 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
653 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
650 # forever. We disable reset_retry_count completely and reset in
654 # forever. We disable reset_retry_count completely and reset in
651 # http_error_auth_reqed instead.
655 # http_error_auth_reqed instead.
652 pass
656 pass
653
657
654 def http_error_auth_reqed(self, auth_header, host, req, headers):
658 def http_error_auth_reqed(self, auth_header, host, req, headers):
655 # Reset the retry counter once for each request.
659 # Reset the retry counter once for each request.
656 if req is not self.retried_req:
660 if req is not self.retried_req:
657 self.retried_req = req
661 self.retried_req = req
658 self.retried = 0
662 self.retried = 0
659 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
663 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
660 # it doesn't know about the auth type requested. This can happen if
664 # it doesn't know about the auth type requested. This can happen if
661 # somebody is using BasicAuth and types a bad password.
665 # somebody is using BasicAuth and types a bad password.
662 try:
666 try:
663 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
667 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
664 self, auth_header, host, req, headers)
668 self, auth_header, host, req, headers)
665 except ValueError, inst:
669 except ValueError, inst:
666 arg = inst.args[0]
670 arg = inst.args[0]
667 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
671 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
668 return
672 return
669 raise
673 raise
670
674
671 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
675 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
672 def __init__(self, *args, **kwargs):
676 def __init__(self, *args, **kwargs):
673 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
677 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
674 self.retried_req = None
678 self.retried_req = None
675
679
676 def reset_retry_count(self):
680 def reset_retry_count(self):
677 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
681 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
678 # forever. We disable reset_retry_count completely and reset in
682 # forever. We disable reset_retry_count completely and reset in
679 # http_error_auth_reqed instead.
683 # http_error_auth_reqed instead.
680 pass
684 pass
681
685
682 def http_error_auth_reqed(self, auth_header, host, req, headers):
686 def http_error_auth_reqed(self, auth_header, host, req, headers):
683 # Reset the retry counter once for each request.
687 # Reset the retry counter once for each request.
684 if req is not self.retried_req:
688 if req is not self.retried_req:
685 self.retried_req = req
689 self.retried_req = req
686 self.retried = 0
690 self.retried = 0
687 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
691 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
688 self, auth_header, host, req, headers)
692 self, auth_header, host, req, headers)
689
693
690 def getauthinfo(path):
694 def getauthinfo(path):
691 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
695 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
692 if not urlpath:
696 if not urlpath:
693 urlpath = '/'
697 urlpath = '/'
694 if scheme != 'file':
698 if scheme != 'file':
695 # XXX: why are we quoting the path again with some smart
699 # XXX: why are we quoting the path again with some smart
696 # heuristic here? Anyway, it cannot be done with file://
700 # heuristic here? Anyway, it cannot be done with file://
697 # urls since path encoding is os/fs dependent (see
701 # urls since path encoding is os/fs dependent (see
698 # urllib.pathname2url() for details).
702 # urllib.pathname2url() for details).
699 urlpath = quotepath(urlpath)
703 urlpath = quotepath(urlpath)
700 host, port, user, passwd = netlocsplit(netloc)
704 host, port, user, passwd = netlocsplit(netloc)
701
705
702 # urllib cannot handle URLs with embedded user or passwd
706 # urllib cannot handle URLs with embedded user or passwd
703 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
707 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
704 urlpath, query, frag))
708 urlpath, query, frag))
705 if user:
709 if user:
706 netloc = host
710 netloc = host
707 if port:
711 if port:
708 netloc += ':' + port
712 netloc += ':' + port
709 # Python < 2.4.3 uses only the netloc to search for a password
713 # Python < 2.4.3 uses only the netloc to search for a password
710 authinfo = (None, (url, netloc), user, passwd or '')
714 authinfo = (None, (url, netloc), user, passwd or '')
711 else:
715 else:
712 authinfo = None
716 authinfo = None
713 return url, authinfo
717 return url, authinfo
714
718
715 handlerfuncs = []
719 handlerfuncs = []
716
720
717 def opener(ui, authinfo=None):
721 def opener(ui, authinfo=None):
718 '''
722 '''
719 construct an opener suitable for urllib2
723 construct an opener suitable for urllib2
720 authinfo will be added to the password manager
724 authinfo will be added to the password manager
721 '''
725 '''
722 handlers = [httphandler()]
726 handlers = [httphandler()]
723 if has_https:
727 if has_https:
724 handlers.append(httpshandler(ui))
728 handlers.append(httpshandler(ui))
725
729
726 handlers.append(proxyhandler(ui))
730 handlers.append(proxyhandler(ui))
727
731
728 passmgr = passwordmgr(ui)
732 passmgr = passwordmgr(ui)
729 if authinfo is not None:
733 if authinfo is not None:
730 passmgr.add_password(*authinfo)
734 passmgr.add_password(*authinfo)
731 user, passwd = authinfo[2:4]
735 user, passwd = authinfo[2:4]
732 ui.debug('http auth: user %s, password %s\n' %
736 ui.debug('http auth: user %s, password %s\n' %
733 (user, passwd and '*' * len(passwd) or 'not set'))
737 (user, passwd and '*' * len(passwd) or 'not set'))
734
738
735 handlers.extend((httpbasicauthhandler(passmgr),
739 handlers.extend((httpbasicauthhandler(passmgr),
736 httpdigestauthhandler(passmgr)))
740 httpdigestauthhandler(passmgr)))
737 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
741 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
738 opener = urllib2.build_opener(*handlers)
742 opener = urllib2.build_opener(*handlers)
739
743
740 # 1.0 here is the _protocol_ version
744 # 1.0 here is the _protocol_ version
741 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
745 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
742 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
746 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
743 return opener
747 return opener
744
748
745 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
749 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
746
750
747 def open(ui, url, data=None):
751 def open(ui, url, data=None):
748 scheme = None
752 scheme = None
749 m = scheme_re.search(url)
753 m = scheme_re.search(url)
750 if m:
754 if m:
751 scheme = m.group(1).lower()
755 scheme = m.group(1).lower()
752 if not scheme:
756 if not scheme:
753 path = util.normpath(os.path.abspath(url))
757 path = util.normpath(os.path.abspath(url))
754 url = 'file://' + urllib.pathname2url(path)
758 url = 'file://' + urllib.pathname2url(path)
755 authinfo = None
759 authinfo = None
756 else:
760 else:
757 url, authinfo = getauthinfo(url)
761 url, authinfo = getauthinfo(url)
758 return opener(ui, authinfo).open(url, data)
762 return opener(ui, authinfo).open(url, data)
@@ -1,269 +1,275 b''
1 Proper https client requires the built-in ssl from Python 2.6.
1 Proper https client requires the built-in ssl from Python 2.6.
2
2
3 $ "$TESTDIR/hghave" ssl || exit 80
3 $ "$TESTDIR/hghave" ssl || exit 80
4
4
5 Certificates created with:
5 Certificates created with:
6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
6 printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
7 openssl req -newkey rsa:512 -keyout priv.pem -nodes -x509 -days 9000 -out pub.pem
8 Can be dumped with:
8 Can be dumped with:
9 openssl x509 -in pub.pem -text
9 openssl x509 -in pub.pem -text
10
10
11 $ cat << EOT > priv.pem
11 $ cat << EOT > priv.pem
12 > -----BEGIN PRIVATE KEY-----
12 > -----BEGIN PRIVATE KEY-----
13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
13 > MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEApjCWeYGrIa/Vo7LH
14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
14 > aRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8
15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
15 > j/xgSwIDAQABAkBxHC6+Qlf0VJXGlb6NL16yEVVTQxqDS6hA9zqu6TZjrr0YMfzc
16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
16 > EGNIiZGt7HCBL0zO+cPDg/LeCZc6HQhf0KrhAiEAzlJq4hWWzvguWFIJWSoBeBUG
17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
17 > MF1ACazQO7PYE8M0qfECIQDONHHP0SKZzz/ZwBZcAveC5K61f/v9hONFwbeYulzR
18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
18 > +wIgc9SvbtgB/5Yzpp//4ZAEnR7oh5SClCvyB+KSx52K3nECICbhQphhoXmI10wy
19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
19 > aMTellaq0bpNMHFDziqH9RsqAHhjAiEAgYGxfzkftt5IUUn/iFK89aaIpyrpuaAh
20 > HY8gUVkVRVs=
20 > HY8gUVkVRVs=
21 > -----END PRIVATE KEY-----
21 > -----END PRIVATE KEY-----
22 > EOT
22 > EOT
23
23
24 $ cat << EOT > pub.pem
24 $ cat << EOT > pub.pem
25 > -----BEGIN CERTIFICATE-----
25 > -----BEGIN CERTIFICATE-----
26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
26 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
27 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
28 > MTAxNDIwMzAxNFoXDTM1MDYwNTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0
29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
29 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
30 > ADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnKEUm34rDaXQd4lxxX
31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
31 > 6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA+amm
32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
32 > r24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQw
33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
33 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAFArvQFiAZJgQczRsbYlG1xl
34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
34 > t+truk37w5B3m3Ick1ntRcQrqs+hf0CO1q6Squ144geYaQ8CDirSR92fICELI1c=
35 > -----END CERTIFICATE-----
35 > -----END CERTIFICATE-----
36 > EOT
36 > EOT
37 $ cat priv.pem pub.pem >> server.pem
37 $ cat priv.pem pub.pem >> server.pem
38 $ PRIV=`pwd`/server.pem
38 $ PRIV=`pwd`/server.pem
39
39
40 $ cat << EOT > pub-other.pem
40 $ cat << EOT > pub-other.pem
41 > -----BEGIN CERTIFICATE-----
41 > -----BEGIN CERTIFICATE-----
42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
42 > MIIBqzCCAVWgAwIBAgIJALwZS731c/ORMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNV
43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
43 > BAMMCWxvY2FsaG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEw
44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
44 > MTAxNDIwNDUxNloXDTM1MDYwNTIwNDUxNlowMTESMBAGA1UEAwwJbG9jYWxob3N0
45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
45 > MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhvc3QwXDANBgkqhkiG9w0BAQEFAANL
46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
46 > ADBIAkEAsxsapLbHrqqUKuQBxdpK4G3m2LjtyrTSdpzzzFlecxd5yhNP6AyWrufo
47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
47 > K4VMGo2xlu9xOo88nDSUNSKPuD09MwIDAQABo1AwTjAdBgNVHQ4EFgQUoIB1iMhN
48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
48 > y868rpQ2qk9dHnU6ebswHwYDVR0jBBgwFoAUoIB1iMhNy868rpQ2qk9dHnU6ebsw
49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
49 > DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJ544f125CsE7J2t55PdFaF6
50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
50 > bBlNBb91FCywBgSjhBjf+GG3TNPwrPdc3yqeq+hzJiuInqbOBv9abmMyq8Wsoig=
51 > -----END CERTIFICATE-----
51 > -----END CERTIFICATE-----
52 > EOT
52 > EOT
53
53
54 pub.pem patched with other notBefore / notAfter:
54 pub.pem patched with other notBefore / notAfter:
55
55
56 $ cat << EOT > pub-not-yet.pem
56 $ cat << EOT > pub-not-yet.pem
57 > -----BEGIN CERTIFICATE-----
57 > -----BEGIN CERTIFICATE-----
58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
58 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
59 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTM1MDYwNTIwMzAxNFoXDTM1MDYw
60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
60 > NTIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
61 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
62 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
63 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
64 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJXV41gWnkgC7jcpPpFRSUSZaxyzrXmD1CIqQf0WgVDb
65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
65 > /12E0vR2DuZitgzUYtBaofM81aTtc0a2/YsrmqePGm0=
66 > -----END CERTIFICATE-----
66 > -----END CERTIFICATE-----
67 > EOT
67 > EOT
68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
68 $ cat priv.pem pub-not-yet.pem > server-not-yet.pem
69
69
70 $ cat << EOT > pub-expired.pem
70 $ cat << EOT > pub-expired.pem
71 > -----BEGIN CERTIFICATE-----
71 > -----BEGIN CERTIFICATE-----
72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
72 > MIIBqzCCAVWgAwIBAgIJANAXFFyWjGnRMA0GCSqGSIb3DQEBBQUAMDExEjAQBgNVBAMMCWxvY2Fs
73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
73 > aG9zdDEbMBkGCSqGSIb3DQEJARYMaGdAbG9jYWxob3N0MB4XDTEwMTAxNDIwMzAxNFoXDTEwMTAx
74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
74 > NDIwMzAxNFowMTESMBAGA1UEAwwJbG9jYWxob3N0MRswGQYJKoZIhvcNAQkBFgxoZ0Bsb2NhbGhv
75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
75 > c3QwXDANBgkqhkiG9w0BAQEFAANLADBIAkEApjCWeYGrIa/Vo7LHaRF8ou0tbgHKE33Use/whCnK
76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
76 > EUm34rDaXQd4lxxX6aDWg06n9tiVStAKTgQAHJY8j/xgSwIDAQABo1AwTjAdBgNVHQ4EFgQUE6sA
77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
77 > +ammr24dGX0kpjxOgO45hzQwHwYDVR0jBBgwFoAUE6sA+ammr24dGX0kpjxOgO45hzQwDAYDVR0T
78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
78 > BAUwAwEB/zANBgkqhkiG9w0BAQUFAANBAJfk57DTRf2nUbYaMSlVAARxMNbFGOjQhAUtY400GhKt
79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
79 > 2uiKCNGKXVXD3AHWe13yHc5KttzbHQStE5Nm/DlWBWQ=
80 > -----END CERTIFICATE-----
80 > -----END CERTIFICATE-----
81 > EOT
81 > EOT
82 $ cat priv.pem pub-expired.pem > server-expired.pem
82 $ cat priv.pem pub-expired.pem > server-expired.pem
83
83
84 $ hg init test
84 $ hg init test
85 $ cd test
85 $ cd test
86 $ echo foo>foo
86 $ echo foo>foo
87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
87 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
88 $ echo foo>foo.d/foo
88 $ echo foo>foo.d/foo
89 $ echo bar>foo.d/bAr.hg.d/BaR
89 $ echo bar>foo.d/bAr.hg.d/BaR
90 $ echo bar>foo.d/baR.d.hg/bAR
90 $ echo bar>foo.d/baR.d.hg/bAR
91 $ hg commit -A -m 1
91 $ hg commit -A -m 1
92 adding foo
92 adding foo
93 adding foo.d/bAr.hg.d/BaR
93 adding foo.d/bAr.hg.d/BaR
94 adding foo.d/baR.d.hg/bAR
94 adding foo.d/baR.d.hg/bAR
95 adding foo.d/foo
95 adding foo.d/foo
96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
96 $ hg serve -p $HGPORT -d --pid-file=../hg0.pid --certificate=$PRIV
97 $ cat ../hg0.pid >> $DAEMON_PIDS
97 $ cat ../hg0.pid >> $DAEMON_PIDS
98
98
99 cacert not found
100
101 $ hg in --config web.cacerts=no-such.pem https://localhost:$HGPORT/
102 abort: could not find web.cacerts: no-such.pem
103 [255]
104
99 Test server address cannot be reused
105 Test server address cannot be reused
100
106
101 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
107 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
102 abort: cannot start server at ':$HGPORT': Address already in use
108 abort: cannot start server at ':$HGPORT': Address already in use
103 [255]
109 [255]
104 $ cd ..
110 $ cd ..
105
111
106 clone via pull
112 clone via pull
107
113
108 $ hg clone https://localhost:$HGPORT/ copy-pull
114 $ hg clone https://localhost:$HGPORT/ copy-pull
109 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
115 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
110 requesting all changes
116 requesting all changes
111 adding changesets
117 adding changesets
112 adding manifests
118 adding manifests
113 adding file changes
119 adding file changes
114 added 1 changesets with 4 changes to 4 files
120 added 1 changesets with 4 changes to 4 files
115 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
121 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
116 updating to branch default
122 updating to branch default
117 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
123 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 $ hg verify -R copy-pull
124 $ hg verify -R copy-pull
119 checking changesets
125 checking changesets
120 checking manifests
126 checking manifests
121 crosschecking files in changesets and manifests
127 crosschecking files in changesets and manifests
122 checking files
128 checking files
123 4 files, 1 changesets, 4 total revisions
129 4 files, 1 changesets, 4 total revisions
124 $ cd test
130 $ cd test
125 $ echo bar > bar
131 $ echo bar > bar
126 $ hg commit -A -d '1 0' -m 2
132 $ hg commit -A -d '1 0' -m 2
127 adding bar
133 adding bar
128 $ cd ..
134 $ cd ..
129
135
130 pull without cacert
136 pull without cacert
131
137
132 $ cd copy-pull
138 $ cd copy-pull
133 $ echo '[hooks]' >> .hg/hgrc
139 $ echo '[hooks]' >> .hg/hgrc
134 $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc
140 $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc
135 $ hg pull
141 $ hg pull
136 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
142 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
137 pulling from https://localhost:$HGPORT/
143 pulling from https://localhost:$HGPORT/
138 searching for changes
144 searching for changes
139 adding changesets
145 adding changesets
140 adding manifests
146 adding manifests
141 adding file changes
147 adding file changes
142 added 1 changesets with 1 changes to 1 files
148 added 1 changesets with 1 changes to 1 files
143 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
149 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
144 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
150 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
145 (run 'hg update' to get a working copy)
151 (run 'hg update' to get a working copy)
146 $ cd ..
152 $ cd ..
147
153
148 cacert configured in local repo
154 cacert configured in local repo
149
155
150 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
156 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
151 $ echo "[web]" >> copy-pull/.hg/hgrc
157 $ echo "[web]" >> copy-pull/.hg/hgrc
152 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
158 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
153 $ hg -R copy-pull pull --traceback
159 $ hg -R copy-pull pull --traceback
154 pulling from https://localhost:$HGPORT/
160 pulling from https://localhost:$HGPORT/
155 searching for changes
161 searching for changes
156 no changes found
162 no changes found
157 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
163 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
158
164
159 cacert configured globally, also testing expansion of environment
165 cacert configured globally, also testing expansion of environment
160 variables in the filename
166 variables in the filename
161
167
162 $ echo "[web]" >> $HGRCPATH
168 $ echo "[web]" >> $HGRCPATH
163 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
169 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
164 $ P=`pwd` hg -R copy-pull pull
170 $ P=`pwd` hg -R copy-pull pull
165 pulling from https://localhost:$HGPORT/
171 pulling from https://localhost:$HGPORT/
166 searching for changes
172 searching for changes
167 no changes found
173 no changes found
168 $ P=`pwd` hg -R copy-pull pull --insecure
174 $ P=`pwd` hg -R copy-pull pull --insecure
169 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
175 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
170 pulling from https://localhost:$HGPORT/
176 pulling from https://localhost:$HGPORT/
171 searching for changes
177 searching for changes
172 no changes found
178 no changes found
173
179
174 cacert mismatch
180 cacert mismatch
175
181
176 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
182 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
177 abort: 127.0.0.1 certificate error: certificate is for localhost (use --insecure to connect insecurely)
183 abort: 127.0.0.1 certificate error: certificate is for localhost (use --insecure to connect insecurely)
178 [255]
184 [255]
179 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
185 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
180 warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
186 warning: 127.0.0.1 certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
181 pulling from https://127.0.0.1:$HGPORT/
187 pulling from https://127.0.0.1:$HGPORT/
182 searching for changes
188 searching for changes
183 no changes found
189 no changes found
184 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
190 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
185 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
191 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
186 [255]
192 [255]
187 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
193 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
188 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
194 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
189 pulling from https://localhost:$HGPORT/
195 pulling from https://localhost:$HGPORT/
190 searching for changes
196 searching for changes
191 no changes found
197 no changes found
192
198
193 Test server cert which isn't valid yet
199 Test server cert which isn't valid yet
194
200
195 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
201 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
196 $ cat hg1.pid >> $DAEMON_PIDS
202 $ cat hg1.pid >> $DAEMON_PIDS
197 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
203 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
198 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
204 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
199 [255]
205 [255]
200
206
201 Test server cert which no longer is valid
207 Test server cert which no longer is valid
202
208
203 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
209 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
204 $ cat hg2.pid >> $DAEMON_PIDS
210 $ cat hg2.pid >> $DAEMON_PIDS
205 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
211 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
206 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
212 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
207 [255]
213 [255]
208
214
209 Fingerprints
215 Fingerprints
210
216
211 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
217 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
212 $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc
218 $ echo "localhost = 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca" >> copy-pull/.hg/hgrc
213 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
219 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
214
220
215 - works without cacerts
221 - works without cacerts
216 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
222 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
217 5fed3813f7f5
223 5fed3813f7f5
218
224
219 - fails when cert doesn't match hostname (port is ignored)
225 - fails when cert doesn't match hostname (port is ignored)
220 $ hg -R copy-pull id https://localhost:$HGPORT1/
226 $ hg -R copy-pull id https://localhost:$HGPORT1/
221 abort: invalid certificate for localhost with fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
227 abort: invalid certificate for localhost with fingerprint 28:ff:71:bf:65:31:14:23:ad:62:92:b4:0e:31:99:18:fc:83:e3:9b
222 [255]
228 [255]
223
229
224 - ignores that certificate doesn't match hostname
230 - ignores that certificate doesn't match hostname
225 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
231 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
226 5fed3813f7f5
232 5fed3813f7f5
227
233
228 Prepare for connecting through proxy
234 Prepare for connecting through proxy
229
235
230 $ kill `cat hg1.pid`
236 $ kill `cat hg1.pid`
231 $ sleep 1
237 $ sleep 1
232
238
233 $ ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
239 $ ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
234 $ echo $! > proxy.pid)
240 $ echo $! > proxy.pid)
235 $ cat proxy.pid >> $DAEMON_PIDS
241 $ cat proxy.pid >> $DAEMON_PIDS
236 $ sleep 2
242 $ sleep 2
237
243
238 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
244 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
239 $ echo "always=True" >> copy-pull/.hg/hgrc
245 $ echo "always=True" >> copy-pull/.hg/hgrc
240 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
246 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
241 $ echo "localhost =" >> copy-pull/.hg/hgrc
247 $ echo "localhost =" >> copy-pull/.hg/hgrc
242
248
243 Test unvalidated https through proxy
249 Test unvalidated https through proxy
244
250
245 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
251 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
246 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
252 warning: localhost certificate with fingerprint 91:4f:1a:ff:87:24:9c:09:b6:85:9b:88:b1:90:6d:30:75:64:91:ca not verified (check hostfingerprints or web.cacerts config setting)
247 pulling from https://localhost:$HGPORT/
253 pulling from https://localhost:$HGPORT/
248 searching for changes
254 searching for changes
249 no changes found
255 no changes found
250
256
251 Test https with cacert and fingerprint through proxy
257 Test https with cacert and fingerprint through proxy
252
258
253 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
259 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
254 pulling from https://localhost:$HGPORT/
260 pulling from https://localhost:$HGPORT/
255 searching for changes
261 searching for changes
256 no changes found
262 no changes found
257 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
263 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
258 pulling from https://127.0.0.1:$HGPORT/
264 pulling from https://127.0.0.1:$HGPORT/
259 searching for changes
265 searching for changes
260 no changes found
266 no changes found
261
267
262 Test https with cert problems through proxy
268 Test https with cert problems through proxy
263
269
264 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
270 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
265 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
271 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
266 [255]
272 [255]
267 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
273 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
268 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
274 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
269 [255]
275 [255]
General Comments 0
You need to be logged in to leave comments. Login now