##// END OF EJS Templates
url: merge BetterHTTPS with httpsconnection to get some proxy https validation
Mads Kiilerich -
r13424:08f9c587 default
parent child Browse files
Show More
@@ -1,767 +1,758 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 BetterHTTPS(httplib.HTTPSConnection):
545 class httpsconnection(httplib.HTTPSConnection):
546 send = keepalive.safesend
546 response_class = keepalive.HTTPResponse
547 # must be able to send big bundle as stream.
548 send = _gen_sendfile(keepalive.safesend)
549 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
547
550
548 def connect(self):
551 def connect(self):
549 self.sock = _create_connection((self.host, self.port))
552 self.sock = _create_connection((self.host, self.port))
550
553
551 host = self.host
554 host = self.host
555 if self.realhostport: # use CONNECT proxy
556 something = _generic_proxytunnel(self)
557 host = self.realhostport.rsplit(':', 1)[0]
558
552 cacerts = self.ui.config('web', 'cacerts')
559 cacerts = self.ui.config('web', 'cacerts')
553 hostfingerprint = self.ui.config('hostfingerprints', host)
560 hostfingerprint = self.ui.config('hostfingerprints', host)
554
561
555 if cacerts and not hostfingerprint:
562 if cacerts and not hostfingerprint:
556 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
563 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
557 self.cert_file, cert_reqs=CERT_REQUIRED,
564 self.cert_file, cert_reqs=CERT_REQUIRED,
558 ca_certs=util.expandpath(cacerts))
565 ca_certs=util.expandpath(cacerts))
559 msg = _verifycert(self.sock.getpeercert(), host)
566 msg = _verifycert(self.sock.getpeercert(), host)
560 if msg:
567 if msg:
561 raise util.Abort(_('%s certificate error: %s '
568 raise util.Abort(_('%s certificate error: %s '
562 '(use --insecure to connect '
569 '(use --insecure to connect '
563 'insecurely)') % (host, msg))
570 'insecurely)') % (host, msg))
564 self.ui.debug('%s certificate successfully verified\n' % host)
571 self.ui.debug('%s certificate successfully verified\n' % host)
565 else:
572 else:
566 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
573 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
567 self.cert_file)
574 self.cert_file)
568 if hasattr(self.sock, 'getpeercert'):
575 if hasattr(self.sock, 'getpeercert'):
569 peercert = self.sock.getpeercert(True)
576 peercert = self.sock.getpeercert(True)
570 peerfingerprint = util.sha1(peercert).hexdigest()
577 peerfingerprint = util.sha1(peercert).hexdigest()
571 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
578 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
572 for x in xrange(0, len(peerfingerprint), 2)])
579 for x in xrange(0, len(peerfingerprint), 2)])
573 if hostfingerprint:
580 if hostfingerprint:
574 if peerfingerprint.lower() != \
581 if peerfingerprint.lower() != \
575 hostfingerprint.replace(':', '').lower():
582 hostfingerprint.replace(':', '').lower():
576 raise util.Abort(_('invalid certificate for %s '
583 raise util.Abort(_('invalid certificate for %s '
577 'with fingerprint %s') %
584 'with fingerprint %s') %
578 (host, nicefingerprint))
585 (host, nicefingerprint))
579 self.ui.debug('%s certificate matched fingerprint %s\n' %
586 self.ui.debug('%s certificate matched fingerprint %s\n' %
580 (host, nicefingerprint))
587 (host, nicefingerprint))
581 else:
588 else:
582 self.ui.warn(_('warning: %s certificate '
589 self.ui.warn(_('warning: %s certificate '
583 'with fingerprint %s not verified '
590 'with fingerprint %s not verified '
584 '(check hostfingerprints or web.cacerts '
591 '(check hostfingerprints or web.cacerts '
585 'config setting)\n') %
592 'config setting)\n') %
586 (host, nicefingerprint))
593 (host, nicefingerprint))
587 else: # python 2.5 ?
594 else: # python 2.5 ?
588 if hostfingerprint:
595 if hostfingerprint:
589 raise util.Abort(_('no certificate for %s with '
596 raise util.Abort(_('no certificate for %s with '
590 'configured hostfingerprint') % host)
597 'configured hostfingerprint') % host)
591 self.ui.warn(_('warning: %s certificate not verified '
598 self.ui.warn(_('warning: %s certificate not verified '
592 '(check web.cacerts config setting)\n') %
599 '(check web.cacerts config setting)\n') %
593 host)
600 host)
594
601
595 class httpsconnection(BetterHTTPS):
596 response_class = keepalive.HTTPResponse
597 # must be able to send big bundle as stream.
598 send = _gen_sendfile(BetterHTTPS.send)
599 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
600
601 def connect(self):
602 if self.realhostport: # use CONNECT proxy
603 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
604 self.sock.connect((self.host, self.port))
605 if _generic_proxytunnel(self):
606 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
607 self.cert_file)
608 else:
609 BetterHTTPS.connect(self)
610
611 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
602 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
612 def __init__(self, ui):
603 def __init__(self, ui):
613 keepalive.KeepAliveHandler.__init__(self)
604 keepalive.KeepAliveHandler.__init__(self)
614 urllib2.HTTPSHandler.__init__(self)
605 urllib2.HTTPSHandler.__init__(self)
615 self.ui = ui
606 self.ui = ui
616 self.pwmgr = passwordmgr(self.ui)
607 self.pwmgr = passwordmgr(self.ui)
617
608
618 def _start_transaction(self, h, req):
609 def _start_transaction(self, h, req):
619 _generic_start_transaction(self, h, req)
610 _generic_start_transaction(self, h, req)
620 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
611 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
621
612
622 def https_open(self, req):
613 def https_open(self, req):
623 res = readauthforuri(self.ui, req.get_full_url())
614 res = readauthforuri(self.ui, req.get_full_url())
624 if res:
615 if res:
625 group, auth = res
616 group, auth = res
626 self.auth = auth
617 self.auth = auth
627 self.ui.debug("using auth.%s.* for authentication\n" % group)
618 self.ui.debug("using auth.%s.* for authentication\n" % group)
628 else:
619 else:
629 self.auth = None
620 self.auth = None
630 return self.do_open(self._makeconnection, req)
621 return self.do_open(self._makeconnection, req)
631
622
632 def _makeconnection(self, host, port=None, *args, **kwargs):
623 def _makeconnection(self, host, port=None, *args, **kwargs):
633 keyfile = None
624 keyfile = None
634 certfile = None
625 certfile = None
635
626
636 if len(args) >= 1: # key_file
627 if len(args) >= 1: # key_file
637 keyfile = args[0]
628 keyfile = args[0]
638 if len(args) >= 2: # cert_file
629 if len(args) >= 2: # cert_file
639 certfile = args[1]
630 certfile = args[1]
640 args = args[2:]
631 args = args[2:]
641
632
642 # if the user has specified different key/cert files in
633 # if the user has specified different key/cert files in
643 # hgrc, we prefer these
634 # hgrc, we prefer these
644 if self.auth and 'key' in self.auth and 'cert' in self.auth:
635 if self.auth and 'key' in self.auth and 'cert' in self.auth:
645 keyfile = self.auth['key']
636 keyfile = self.auth['key']
646 certfile = self.auth['cert']
637 certfile = self.auth['cert']
647
638
648 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
639 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
649 conn.ui = self.ui
640 conn.ui = self.ui
650 return conn
641 return conn
651
642
652 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
643 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
653 def __init__(self, *args, **kwargs):
644 def __init__(self, *args, **kwargs):
654 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
645 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
655 self.retried_req = None
646 self.retried_req = None
656
647
657 def reset_retry_count(self):
648 def reset_retry_count(self):
658 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
649 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
659 # forever. We disable reset_retry_count completely and reset in
650 # forever. We disable reset_retry_count completely and reset in
660 # http_error_auth_reqed instead.
651 # http_error_auth_reqed instead.
661 pass
652 pass
662
653
663 def http_error_auth_reqed(self, auth_header, host, req, headers):
654 def http_error_auth_reqed(self, auth_header, host, req, headers):
664 # Reset the retry counter once for each request.
655 # Reset the retry counter once for each request.
665 if req is not self.retried_req:
656 if req is not self.retried_req:
666 self.retried_req = req
657 self.retried_req = req
667 self.retried = 0
658 self.retried = 0
668 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
659 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
669 # it doesn't know about the auth type requested. This can happen if
660 # it doesn't know about the auth type requested. This can happen if
670 # somebody is using BasicAuth and types a bad password.
661 # somebody is using BasicAuth and types a bad password.
671 try:
662 try:
672 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
663 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
673 self, auth_header, host, req, headers)
664 self, auth_header, host, req, headers)
674 except ValueError, inst:
665 except ValueError, inst:
675 arg = inst.args[0]
666 arg = inst.args[0]
676 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
667 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
677 return
668 return
678 raise
669 raise
679
670
680 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
671 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
681 def __init__(self, *args, **kwargs):
672 def __init__(self, *args, **kwargs):
682 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
673 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
683 self.retried_req = None
674 self.retried_req = None
684
675
685 def reset_retry_count(self):
676 def reset_retry_count(self):
686 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
677 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
687 # forever. We disable reset_retry_count completely and reset in
678 # forever. We disable reset_retry_count completely and reset in
688 # http_error_auth_reqed instead.
679 # http_error_auth_reqed instead.
689 pass
680 pass
690
681
691 def http_error_auth_reqed(self, auth_header, host, req, headers):
682 def http_error_auth_reqed(self, auth_header, host, req, headers):
692 # Reset the retry counter once for each request.
683 # Reset the retry counter once for each request.
693 if req is not self.retried_req:
684 if req is not self.retried_req:
694 self.retried_req = req
685 self.retried_req = req
695 self.retried = 0
686 self.retried = 0
696 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
687 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
697 self, auth_header, host, req, headers)
688 self, auth_header, host, req, headers)
698
689
699 def getauthinfo(path):
690 def getauthinfo(path):
700 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
691 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
701 if not urlpath:
692 if not urlpath:
702 urlpath = '/'
693 urlpath = '/'
703 if scheme != 'file':
694 if scheme != 'file':
704 # XXX: why are we quoting the path again with some smart
695 # XXX: why are we quoting the path again with some smart
705 # heuristic here? Anyway, it cannot be done with file://
696 # heuristic here? Anyway, it cannot be done with file://
706 # urls since path encoding is os/fs dependent (see
697 # urls since path encoding is os/fs dependent (see
707 # urllib.pathname2url() for details).
698 # urllib.pathname2url() for details).
708 urlpath = quotepath(urlpath)
699 urlpath = quotepath(urlpath)
709 host, port, user, passwd = netlocsplit(netloc)
700 host, port, user, passwd = netlocsplit(netloc)
710
701
711 # urllib cannot handle URLs with embedded user or passwd
702 # urllib cannot handle URLs with embedded user or passwd
712 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
703 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
713 urlpath, query, frag))
704 urlpath, query, frag))
714 if user:
705 if user:
715 netloc = host
706 netloc = host
716 if port:
707 if port:
717 netloc += ':' + port
708 netloc += ':' + port
718 # Python < 2.4.3 uses only the netloc to search for a password
709 # Python < 2.4.3 uses only the netloc to search for a password
719 authinfo = (None, (url, netloc), user, passwd or '')
710 authinfo = (None, (url, netloc), user, passwd or '')
720 else:
711 else:
721 authinfo = None
712 authinfo = None
722 return url, authinfo
713 return url, authinfo
723
714
724 handlerfuncs = []
715 handlerfuncs = []
725
716
726 def opener(ui, authinfo=None):
717 def opener(ui, authinfo=None):
727 '''
718 '''
728 construct an opener suitable for urllib2
719 construct an opener suitable for urllib2
729 authinfo will be added to the password manager
720 authinfo will be added to the password manager
730 '''
721 '''
731 handlers = [httphandler()]
722 handlers = [httphandler()]
732 if has_https:
723 if has_https:
733 handlers.append(httpshandler(ui))
724 handlers.append(httpshandler(ui))
734
725
735 handlers.append(proxyhandler(ui))
726 handlers.append(proxyhandler(ui))
736
727
737 passmgr = passwordmgr(ui)
728 passmgr = passwordmgr(ui)
738 if authinfo is not None:
729 if authinfo is not None:
739 passmgr.add_password(*authinfo)
730 passmgr.add_password(*authinfo)
740 user, passwd = authinfo[2:4]
731 user, passwd = authinfo[2:4]
741 ui.debug('http auth: user %s, password %s\n' %
732 ui.debug('http auth: user %s, password %s\n' %
742 (user, passwd and '*' * len(passwd) or 'not set'))
733 (user, passwd and '*' * len(passwd) or 'not set'))
743
734
744 handlers.extend((httpbasicauthhandler(passmgr),
735 handlers.extend((httpbasicauthhandler(passmgr),
745 httpdigestauthhandler(passmgr)))
736 httpdigestauthhandler(passmgr)))
746 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
737 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
747 opener = urllib2.build_opener(*handlers)
738 opener = urllib2.build_opener(*handlers)
748
739
749 # 1.0 here is the _protocol_ version
740 # 1.0 here is the _protocol_ version
750 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
741 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
751 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
742 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
752 return opener
743 return opener
753
744
754 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
745 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
755
746
756 def open(ui, url, data=None):
747 def open(ui, url, data=None):
757 scheme = None
748 scheme = None
758 m = scheme_re.search(url)
749 m = scheme_re.search(url)
759 if m:
750 if m:
760 scheme = m.group(1).lower()
751 scheme = m.group(1).lower()
761 if not scheme:
752 if not scheme:
762 path = util.normpath(os.path.abspath(url))
753 path = util.normpath(os.path.abspath(url))
763 url = 'file://' + urllib.pathname2url(path)
754 url = 'file://' + urllib.pathname2url(path)
764 authinfo = None
755 authinfo = None
765 else:
756 else:
766 url, authinfo = getauthinfo(url)
757 url, authinfo = getauthinfo(url)
767 return opener(ui, authinfo).open(url, data)
758 return opener(ui, authinfo).open(url, data)
@@ -1,270 +1,268 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 Test server address cannot be reused
99 Test server address cannot be reused
100
100
101 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
101 $ hg serve -p $HGPORT --certificate=$PRIV 2>&1
102 abort: cannot start server at ':$HGPORT': Address already in use
102 abort: cannot start server at ':$HGPORT': Address already in use
103 [255]
103 [255]
104 $ cd ..
104 $ cd ..
105
105
106 clone via pull
106 clone via pull
107
107
108 $ hg clone https://localhost:$HGPORT/ copy-pull
108 $ 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)
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)
110 requesting all changes
110 requesting all changes
111 adding changesets
111 adding changesets
112 adding manifests
112 adding manifests
113 adding file changes
113 adding file changes
114 added 1 changesets with 4 changes to 4 files
114 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)
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)
116 updating to branch default
116 updating to branch default
117 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
117 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 $ hg verify -R copy-pull
118 $ hg verify -R copy-pull
119 checking changesets
119 checking changesets
120 checking manifests
120 checking manifests
121 crosschecking files in changesets and manifests
121 crosschecking files in changesets and manifests
122 checking files
122 checking files
123 4 files, 1 changesets, 4 total revisions
123 4 files, 1 changesets, 4 total revisions
124 $ cd test
124 $ cd test
125 $ echo bar > bar
125 $ echo bar > bar
126 $ hg commit -A -d '1 0' -m 2
126 $ hg commit -A -d '1 0' -m 2
127 adding bar
127 adding bar
128 $ cd ..
128 $ cd ..
129
129
130 pull without cacert
130 pull without cacert
131
131
132 $ cd copy-pull
132 $ cd copy-pull
133 $ echo '[hooks]' >> .hg/hgrc
133 $ echo '[hooks]' >> .hg/hgrc
134 $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc
134 $ echo "changegroup = python '$TESTDIR'/printenv.py changegroup" >> .hg/hgrc
135 $ hg pull
135 $ 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)
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)
137 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
137 changegroup hook: HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_URL=https://localhost:$HGPORT/
138 pulling from https://localhost:$HGPORT/
138 pulling from https://localhost:$HGPORT/
139 searching for changes
139 searching for changes
140 adding changesets
140 adding changesets
141 adding manifests
141 adding manifests
142 adding file changes
142 adding file changes
143 added 1 changesets with 1 changes to 1 files
143 added 1 changesets with 1 changes to 1 files
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)
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)
145 (run 'hg update' to get a working copy)
145 (run 'hg update' to get a working copy)
146 $ cd ..
146 $ cd ..
147
147
148 cacert configured in local repo
148 cacert configured in local repo
149
149
150 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
150 $ cp copy-pull/.hg/hgrc copy-pull/.hg/hgrc.bu
151 $ echo "[web]" >> copy-pull/.hg/hgrc
151 $ echo "[web]" >> copy-pull/.hg/hgrc
152 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
152 $ echo "cacerts=`pwd`/pub.pem" >> copy-pull/.hg/hgrc
153 $ hg -R copy-pull pull --traceback
153 $ hg -R copy-pull pull --traceback
154 pulling from https://localhost:$HGPORT/
154 pulling from https://localhost:$HGPORT/
155 searching for changes
155 searching for changes
156 no changes found
156 no changes found
157 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
157 $ mv copy-pull/.hg/hgrc.bu copy-pull/.hg/hgrc
158
158
159 cacert configured globally, also testing expansion of environment
159 cacert configured globally, also testing expansion of environment
160 variables in the filename
160 variables in the filename
161
161
162 $ echo "[web]" >> $HGRCPATH
162 $ echo "[web]" >> $HGRCPATH
163 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
163 $ echo 'cacerts=$P/pub.pem' >> $HGRCPATH
164 $ P=`pwd` hg -R copy-pull pull
164 $ P=`pwd` hg -R copy-pull pull
165 pulling from https://localhost:$HGPORT/
165 pulling from https://localhost:$HGPORT/
166 searching for changes
166 searching for changes
167 no changes found
167 no changes found
168 $ P=`pwd` hg -R copy-pull pull --insecure
168 $ 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)
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)
170 pulling from https://localhost:$HGPORT/
170 pulling from https://localhost:$HGPORT/
171 searching for changes
171 searching for changes
172 no changes found
172 no changes found
173
173
174 cacert mismatch
174 cacert mismatch
175
175
176 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/
176 $ 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)
177 abort: 127.0.0.1 certificate error: certificate is for localhost (use --insecure to connect insecurely)
178 [255]
178 [255]
179 $ hg -R copy-pull pull --config web.cacerts=pub.pem https://127.0.0.1:$HGPORT/ --insecure
179 $ 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)
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)
181 pulling from https://127.0.0.1:$HGPORT/
181 pulling from https://127.0.0.1:$HGPORT/
182 searching for changes
182 searching for changes
183 no changes found
183 no changes found
184 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
184 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem
185 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
185 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
186 [255]
186 [255]
187 $ hg -R copy-pull pull --config web.cacerts=pub-other.pem --insecure
187 $ 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)
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)
189 pulling from https://localhost:$HGPORT/
189 pulling from https://localhost:$HGPORT/
190 searching for changes
190 searching for changes
191 no changes found
191 no changes found
192
192
193 Test server cert which isn't valid yet
193 Test server cert which isn't valid yet
194
194
195 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
195 $ hg -R test serve -p $HGPORT1 -d --pid-file=hg1.pid --certificate=server-not-yet.pem
196 $ cat hg1.pid >> $DAEMON_PIDS
196 $ cat hg1.pid >> $DAEMON_PIDS
197 $ hg -R copy-pull pull --config web.cacerts=pub-not-yet.pem https://localhost:$HGPORT1/
197 $ 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)
198 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
199 [255]
199 [255]
200
200
201 Test server cert which no longer is valid
201 Test server cert which no longer is valid
202
202
203 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
203 $ hg -R test serve -p $HGPORT2 -d --pid-file=hg2.pid --certificate=server-expired.pem
204 $ cat hg2.pid >> $DAEMON_PIDS
204 $ cat hg2.pid >> $DAEMON_PIDS
205 $ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
205 $ 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)
206 abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
207 [255]
207 [255]
208
208
209 Fingerprints
209 Fingerprints
210
210
211 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
211 $ 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
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
213 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
213 $ echo "127.0.0.1 = 914f1aff87249c09b6859b88b1906d30756491ca" >> copy-pull/.hg/hgrc
214
214
215 - works without cacerts
215 - works without cacerts
216 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
216 $ hg -R copy-pull id https://localhost:$HGPORT/ --config web.cacerts=
217 5fed3813f7f5
217 5fed3813f7f5
218
218
219 - fails when cert doesn't match hostname (port is ignored)
219 - fails when cert doesn't match hostname (port is ignored)
220 $ hg -R copy-pull id https://localhost:$HGPORT1/
220 $ 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
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
222 [255]
222 [255]
223
223
224 - ignores that certificate doesn't match hostname
224 - ignores that certificate doesn't match hostname
225 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
225 $ hg -R copy-pull id https://127.0.0.1:$HGPORT/
226 5fed3813f7f5
226 5fed3813f7f5
227
227
228 Prepare for connecting through proxy
228 Prepare for connecting through proxy
229
229
230 $ kill `cat hg1.pid`
230 $ kill `cat hg1.pid`
231 $ sleep 1
231 $ sleep 1
232
232
233 $ ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
233 $ ("$TESTDIR/tinyproxy.py" $HGPORT1 localhost >proxy.log 2>&1 </dev/null &
234 $ echo $! > proxy.pid)
234 $ echo $! > proxy.pid)
235 $ cat proxy.pid >> $DAEMON_PIDS
235 $ cat proxy.pid >> $DAEMON_PIDS
236 $ sleep 2
236 $ sleep 2
237
237
238 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
238 $ echo "[http_proxy]" >> copy-pull/.hg/hgrc
239 $ echo "always=True" >> copy-pull/.hg/hgrc
239 $ echo "always=True" >> copy-pull/.hg/hgrc
240 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
240 $ echo "[hostfingerprints]" >> copy-pull/.hg/hgrc
241 $ echo "localhost =" >> copy-pull/.hg/hgrc
241 $ echo "localhost =" >> copy-pull/.hg/hgrc
242
242
243 Test unvalidated https through proxy
243 Test unvalidated https through proxy
244
244
245 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
245 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --insecure --traceback
246 pulling from https://localhost:$HGPORT/
246 pulling from https://localhost:$HGPORT/
247 searching for changes
247 searching for changes
248 no changes found
248 no changes found
249
249
250 Test https with cacert and fingerprint through proxy
250 Test https with cacert and fingerprint through proxy
251
251
252 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
252 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub.pem
253 pulling from https://localhost:$HGPORT/
253 pulling from https://localhost:$HGPORT/
254 searching for changes
254 searching for changes
255 no changes found
255 no changes found
256 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
256 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull https://127.0.0.1:$HGPORT/
257 pulling from https://127.0.0.1:$HGPORT/
257 pulling from https://127.0.0.1:$HGPORT/
258 searching for changes
258 searching for changes
259 no changes found
259 no changes found
260
260
261 Test https with cert problems through proxy
261 Test https with cert problems through proxy
262
262
263 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
263 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-other.pem
264 pulling from https://localhost:$HGPORT/
264 abort: error: _ssl.c:499: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
265 searching for changes
265 [255]
266 no changes found
267 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
266 $ http_proxy=http://localhost:$HGPORT1/ hg -R copy-pull pull --config web.cacerts=pub-expired.pem https://localhost:$HGPORT2/
268 pulling from https://localhost:$HGPORT2/
267 abort: error: _ssl.c:499: error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed
269 searching for changes
268 [255]
270 no changes found
General Comments 0
You need to be logged in to leave comments. Login now