##// END OF EJS Templates
url: check subjectAltName when verifying ssl certificate...
Yuya Nishihara -
r13249:75d0c38a stable
parent child Browse files
Show More
@@ -1,707 +1,719 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 _safe = ('abcdefghijklmnopqrstuvwxyz'
74 _safe = ('abcdefghijklmnopqrstuvwxyz'
75 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
75 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
76 '0123456789' '_.-/')
76 '0123456789' '_.-/')
77 _safeset = None
77 _safeset = None
78 _hex = None
78 _hex = None
79 def quotepath(path):
79 def quotepath(path):
80 '''quote the path part of a URL
80 '''quote the path part of a URL
81
81
82 This is similar to urllib.quote, but it also tries to avoid
82 This is similar to urllib.quote, but it also tries to avoid
83 quoting things twice (inspired by wget):
83 quoting things twice (inspired by wget):
84
84
85 >>> quotepath('abc def')
85 >>> quotepath('abc def')
86 'abc%20def'
86 'abc%20def'
87 >>> quotepath('abc%20def')
87 >>> quotepath('abc%20def')
88 'abc%20def'
88 'abc%20def'
89 >>> quotepath('abc%20 def')
89 >>> quotepath('abc%20 def')
90 'abc%20%20def'
90 'abc%20%20def'
91 >>> quotepath('abc def%20')
91 >>> quotepath('abc def%20')
92 'abc%20def%20'
92 'abc%20def%20'
93 >>> quotepath('abc def%2')
93 >>> quotepath('abc def%2')
94 'abc%20def%252'
94 'abc%20def%252'
95 >>> quotepath('abc def%')
95 >>> quotepath('abc def%')
96 'abc%20def%25'
96 'abc%20def%25'
97 '''
97 '''
98 global _safeset, _hex
98 global _safeset, _hex
99 if _safeset is None:
99 if _safeset is None:
100 _safeset = set(_safe)
100 _safeset = set(_safe)
101 _hex = set('abcdefABCDEF0123456789')
101 _hex = set('abcdefABCDEF0123456789')
102 l = list(path)
102 l = list(path)
103 for i in xrange(len(l)):
103 for i in xrange(len(l)):
104 c = l[i]
104 c = l[i]
105 if (c == '%' and i + 2 < len(l) and
105 if (c == '%' and i + 2 < len(l) and
106 l[i + 1] in _hex and l[i + 2] in _hex):
106 l[i + 1] in _hex and l[i + 2] in _hex):
107 pass
107 pass
108 elif c not in _safeset:
108 elif c not in _safeset:
109 l[i] = '%%%02X' % ord(c)
109 l[i] = '%%%02X' % ord(c)
110 return ''.join(l)
110 return ''.join(l)
111
111
112 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
112 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
113 def __init__(self, ui):
113 def __init__(self, ui):
114 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
114 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
115 self.ui = ui
115 self.ui = ui
116
116
117 def find_user_password(self, realm, authuri):
117 def find_user_password(self, realm, authuri):
118 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
118 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
119 self, realm, authuri)
119 self, realm, authuri)
120 user, passwd = authinfo
120 user, passwd = authinfo
121 if user and passwd:
121 if user and passwd:
122 self._writedebug(user, passwd)
122 self._writedebug(user, passwd)
123 return (user, passwd)
123 return (user, passwd)
124
124
125 if not user:
125 if not user:
126 auth = self.readauthtoken(authuri)
126 auth = self.readauthtoken(authuri)
127 if auth:
127 if auth:
128 user, passwd = auth.get('username'), auth.get('password')
128 user, passwd = auth.get('username'), auth.get('password')
129 if not user or not passwd:
129 if not user or not passwd:
130 if not self.ui.interactive():
130 if not self.ui.interactive():
131 raise util.Abort(_('http authorization required'))
131 raise util.Abort(_('http authorization required'))
132
132
133 self.ui.write(_("http authorization required\n"))
133 self.ui.write(_("http authorization required\n"))
134 self.ui.write(_("realm: %s\n") % realm)
134 self.ui.write(_("realm: %s\n") % realm)
135 if user:
135 if user:
136 self.ui.write(_("user: %s\n") % user)
136 self.ui.write(_("user: %s\n") % user)
137 else:
137 else:
138 user = self.ui.prompt(_("user:"), default=None)
138 user = self.ui.prompt(_("user:"), default=None)
139
139
140 if not passwd:
140 if not passwd:
141 passwd = self.ui.getpass()
141 passwd = self.ui.getpass()
142
142
143 self.add_password(realm, authuri, user, passwd)
143 self.add_password(realm, authuri, user, passwd)
144 self._writedebug(user, passwd)
144 self._writedebug(user, passwd)
145 return (user, passwd)
145 return (user, passwd)
146
146
147 def _writedebug(self, user, passwd):
147 def _writedebug(self, user, passwd):
148 msg = _('http auth: user %s, password %s\n')
148 msg = _('http auth: user %s, password %s\n')
149 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
149 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
150
150
151 def readauthtoken(self, uri):
151 def readauthtoken(self, uri):
152 # Read configuration
152 # Read configuration
153 config = dict()
153 config = dict()
154 for key, val in self.ui.configitems('auth'):
154 for key, val in self.ui.configitems('auth'):
155 if '.' not in key:
155 if '.' not in key:
156 self.ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
156 self.ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
157 continue
157 continue
158 group, setting = key.split('.', 1)
158 group, setting = key.split('.', 1)
159 gdict = config.setdefault(group, dict())
159 gdict = config.setdefault(group, dict())
160 if setting in ('username', 'cert', 'key'):
160 if setting in ('username', 'cert', 'key'):
161 val = util.expandpath(val)
161 val = util.expandpath(val)
162 gdict[setting] = val
162 gdict[setting] = val
163
163
164 # Find the best match
164 # Find the best match
165 scheme, hostpath = uri.split('://', 1)
165 scheme, hostpath = uri.split('://', 1)
166 bestlen = 0
166 bestlen = 0
167 bestauth = None
167 bestauth = None
168 for auth in config.itervalues():
168 for auth in config.itervalues():
169 prefix = auth.get('prefix')
169 prefix = auth.get('prefix')
170 if not prefix:
170 if not prefix:
171 continue
171 continue
172 p = prefix.split('://', 1)
172 p = prefix.split('://', 1)
173 if len(p) > 1:
173 if len(p) > 1:
174 schemes, prefix = [p[0]], p[1]
174 schemes, prefix = [p[0]], p[1]
175 else:
175 else:
176 schemes = (auth.get('schemes') or 'https').split()
176 schemes = (auth.get('schemes') or 'https').split()
177 if (prefix == '*' or hostpath.startswith(prefix)) and \
177 if (prefix == '*' or hostpath.startswith(prefix)) and \
178 len(prefix) > bestlen and scheme in schemes:
178 len(prefix) > bestlen and scheme in schemes:
179 bestlen = len(prefix)
179 bestlen = len(prefix)
180 bestauth = auth
180 bestauth = auth
181 return bestauth
181 return bestauth
182
182
183 class proxyhandler(urllib2.ProxyHandler):
183 class proxyhandler(urllib2.ProxyHandler):
184 def __init__(self, ui):
184 def __init__(self, ui):
185 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
185 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
186 # XXX proxyauthinfo = None
186 # XXX proxyauthinfo = None
187
187
188 if proxyurl:
188 if proxyurl:
189 # proxy can be proper url or host[:port]
189 # proxy can be proper url or host[:port]
190 if not (proxyurl.startswith('http:') or
190 if not (proxyurl.startswith('http:') or
191 proxyurl.startswith('https:')):
191 proxyurl.startswith('https:')):
192 proxyurl = 'http://' + proxyurl + '/'
192 proxyurl = 'http://' + proxyurl + '/'
193 snpqf = urlparse.urlsplit(proxyurl)
193 snpqf = urlparse.urlsplit(proxyurl)
194 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
194 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
195 hpup = netlocsplit(proxynetloc)
195 hpup = netlocsplit(proxynetloc)
196
196
197 proxyhost, proxyport, proxyuser, proxypasswd = hpup
197 proxyhost, proxyport, proxyuser, proxypasswd = hpup
198 if not proxyuser:
198 if not proxyuser:
199 proxyuser = ui.config("http_proxy", "user")
199 proxyuser = ui.config("http_proxy", "user")
200 proxypasswd = ui.config("http_proxy", "passwd")
200 proxypasswd = ui.config("http_proxy", "passwd")
201
201
202 # see if we should use a proxy for this url
202 # see if we should use a proxy for this url
203 no_list = ["localhost", "127.0.0.1"]
203 no_list = ["localhost", "127.0.0.1"]
204 no_list.extend([p.lower() for
204 no_list.extend([p.lower() for
205 p in ui.configlist("http_proxy", "no")])
205 p in ui.configlist("http_proxy", "no")])
206 no_list.extend([p.strip().lower() for
206 no_list.extend([p.strip().lower() for
207 p in os.getenv("no_proxy", '').split(',')
207 p in os.getenv("no_proxy", '').split(',')
208 if p.strip()])
208 if p.strip()])
209 # "http_proxy.always" config is for running tests on localhost
209 # "http_proxy.always" config is for running tests on localhost
210 if ui.configbool("http_proxy", "always"):
210 if ui.configbool("http_proxy", "always"):
211 self.no_list = []
211 self.no_list = []
212 else:
212 else:
213 self.no_list = no_list
213 self.no_list = no_list
214
214
215 proxyurl = urlparse.urlunsplit((
215 proxyurl = urlparse.urlunsplit((
216 proxyscheme, netlocunsplit(proxyhost, proxyport,
216 proxyscheme, netlocunsplit(proxyhost, proxyport,
217 proxyuser, proxypasswd or ''),
217 proxyuser, proxypasswd or ''),
218 proxypath, proxyquery, proxyfrag))
218 proxypath, proxyquery, proxyfrag))
219 proxies = {'http': proxyurl, 'https': proxyurl}
219 proxies = {'http': proxyurl, 'https': proxyurl}
220 ui.debug('proxying through http://%s:%s\n' %
220 ui.debug('proxying through http://%s:%s\n' %
221 (proxyhost, proxyport))
221 (proxyhost, proxyport))
222 else:
222 else:
223 proxies = {}
223 proxies = {}
224
224
225 # urllib2 takes proxy values from the environment and those
225 # urllib2 takes proxy values from the environment and those
226 # will take precedence if found, so drop them
226 # will take precedence if found, so drop them
227 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
227 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
228 try:
228 try:
229 if env in os.environ:
229 if env in os.environ:
230 del os.environ[env]
230 del os.environ[env]
231 except OSError:
231 except OSError:
232 pass
232 pass
233
233
234 urllib2.ProxyHandler.__init__(self, proxies)
234 urllib2.ProxyHandler.__init__(self, proxies)
235 self.ui = ui
235 self.ui = ui
236
236
237 def proxy_open(self, req, proxy, type_):
237 def proxy_open(self, req, proxy, type_):
238 host = req.get_host().split(':')[0]
238 host = req.get_host().split(':')[0]
239 if host in self.no_list:
239 if host in self.no_list:
240 return None
240 return None
241
241
242 # work around a bug in Python < 2.4.2
242 # work around a bug in Python < 2.4.2
243 # (it leaves a "\n" at the end of Proxy-authorization headers)
243 # (it leaves a "\n" at the end of Proxy-authorization headers)
244 baseclass = req.__class__
244 baseclass = req.__class__
245 class _request(baseclass):
245 class _request(baseclass):
246 def add_header(self, key, val):
246 def add_header(self, key, val):
247 if key.lower() == 'proxy-authorization':
247 if key.lower() == 'proxy-authorization':
248 val = val.strip()
248 val = val.strip()
249 return baseclass.add_header(self, key, val)
249 return baseclass.add_header(self, key, val)
250 req.__class__ = _request
250 req.__class__ = _request
251
251
252 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
252 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
253
253
254 class httpsendfile(object):
254 class httpsendfile(object):
255 """This is a wrapper around the objects returned by python's "open".
255 """This is a wrapper around the objects returned by python's "open".
256
256
257 Its purpose is to send file-like objects via HTTP and, to do so, it
257 Its purpose is to send file-like objects via HTTP and, to do so, it
258 defines a __len__ attribute to feed the Content-Length header.
258 defines a __len__ attribute to feed the Content-Length header.
259 """
259 """
260
260
261 def __init__(self, *args, **kwargs):
261 def __init__(self, *args, **kwargs):
262 # We can't just "self._data = open(*args, **kwargs)" here because there
262 # We can't just "self._data = open(*args, **kwargs)" here because there
263 # is an "open" function defined in this module that shadows the global
263 # is an "open" function defined in this module that shadows the global
264 # one
264 # one
265 self._data = __builtin__.open(*args, **kwargs)
265 self._data = __builtin__.open(*args, **kwargs)
266 self.read = self._data.read
266 self.read = self._data.read
267 self.seek = self._data.seek
267 self.seek = self._data.seek
268 self.close = self._data.close
268 self.close = self._data.close
269 self.write = self._data.write
269 self.write = self._data.write
270
270
271 def __len__(self):
271 def __len__(self):
272 return os.fstat(self._data.fileno()).st_size
272 return os.fstat(self._data.fileno()).st_size
273
273
274 def _gen_sendfile(connection):
274 def _gen_sendfile(connection):
275 def _sendfile(self, data):
275 def _sendfile(self, data):
276 # send a file
276 # send a file
277 if isinstance(data, httpsendfile):
277 if isinstance(data, httpsendfile):
278 # if auth required, some data sent twice, so rewind here
278 # if auth required, some data sent twice, so rewind here
279 data.seek(0)
279 data.seek(0)
280 for chunk in util.filechunkiter(data):
280 for chunk in util.filechunkiter(data):
281 connection.send(self, chunk)
281 connection.send(self, chunk)
282 else:
282 else:
283 connection.send(self, data)
283 connection.send(self, data)
284 return _sendfile
284 return _sendfile
285
285
286 has_https = hasattr(urllib2, 'HTTPSHandler')
286 has_https = hasattr(urllib2, 'HTTPSHandler')
287 if has_https:
287 if has_https:
288 try:
288 try:
289 # avoid using deprecated/broken FakeSocket in python 2.6
289 # avoid using deprecated/broken FakeSocket in python 2.6
290 import ssl
290 import ssl
291 _ssl_wrap_socket = ssl.wrap_socket
291 _ssl_wrap_socket = ssl.wrap_socket
292 CERT_REQUIRED = ssl.CERT_REQUIRED
292 CERT_REQUIRED = ssl.CERT_REQUIRED
293 except ImportError:
293 except ImportError:
294 CERT_REQUIRED = 2
294 CERT_REQUIRED = 2
295
295
296 def _ssl_wrap_socket(sock, key_file, cert_file,
296 def _ssl_wrap_socket(sock, key_file, cert_file,
297 cert_reqs=CERT_REQUIRED, ca_certs=None):
297 cert_reqs=CERT_REQUIRED, ca_certs=None):
298 if ca_certs:
298 if ca_certs:
299 raise util.Abort(_(
299 raise util.Abort(_(
300 'certificate checking requires Python 2.6'))
300 'certificate checking requires Python 2.6'))
301
301
302 ssl = socket.ssl(sock, key_file, cert_file)
302 ssl = socket.ssl(sock, key_file, cert_file)
303 return httplib.FakeSocket(sock, ssl)
303 return httplib.FakeSocket(sock, ssl)
304
304
305 try:
305 try:
306 _create_connection = socket.create_connection
306 _create_connection = socket.create_connection
307 except AttributeError:
307 except AttributeError:
308 _GLOBAL_DEFAULT_TIMEOUT = object()
308 _GLOBAL_DEFAULT_TIMEOUT = object()
309
309
310 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
310 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
311 source_address=None):
311 source_address=None):
312 # lifted from Python 2.6
312 # lifted from Python 2.6
313
313
314 msg = "getaddrinfo returns an empty list"
314 msg = "getaddrinfo returns an empty list"
315 host, port = address
315 host, port = address
316 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
316 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
317 af, socktype, proto, canonname, sa = res
317 af, socktype, proto, canonname, sa = res
318 sock = None
318 sock = None
319 try:
319 try:
320 sock = socket.socket(af, socktype, proto)
320 sock = socket.socket(af, socktype, proto)
321 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
321 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
322 sock.settimeout(timeout)
322 sock.settimeout(timeout)
323 if source_address:
323 if source_address:
324 sock.bind(source_address)
324 sock.bind(source_address)
325 sock.connect(sa)
325 sock.connect(sa)
326 return sock
326 return sock
327
327
328 except socket.error, msg:
328 except socket.error, msg:
329 if sock is not None:
329 if sock is not None:
330 sock.close()
330 sock.close()
331
331
332 raise socket.error, msg
332 raise socket.error, msg
333
333
334 class httpconnection(keepalive.HTTPConnection):
334 class httpconnection(keepalive.HTTPConnection):
335 # must be able to send big bundle as stream.
335 # must be able to send big bundle as stream.
336 send = _gen_sendfile(keepalive.HTTPConnection)
336 send = _gen_sendfile(keepalive.HTTPConnection)
337
337
338 def connect(self):
338 def connect(self):
339 if has_https and self.realhostport: # use CONNECT proxy
339 if has_https and self.realhostport: # use CONNECT proxy
340 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
340 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
341 self.sock.connect((self.host, self.port))
341 self.sock.connect((self.host, self.port))
342 if _generic_proxytunnel(self):
342 if _generic_proxytunnel(self):
343 # we do not support client x509 certificates
343 # we do not support client x509 certificates
344 self.sock = _ssl_wrap_socket(self.sock, None, None)
344 self.sock = _ssl_wrap_socket(self.sock, None, None)
345 else:
345 else:
346 keepalive.HTTPConnection.connect(self)
346 keepalive.HTTPConnection.connect(self)
347
347
348 def getresponse(self):
348 def getresponse(self):
349 proxyres = getattr(self, 'proxyres', None)
349 proxyres = getattr(self, 'proxyres', None)
350 if proxyres:
350 if proxyres:
351 if proxyres.will_close:
351 if proxyres.will_close:
352 self.close()
352 self.close()
353 self.proxyres = None
353 self.proxyres = None
354 return proxyres
354 return proxyres
355 return keepalive.HTTPConnection.getresponse(self)
355 return keepalive.HTTPConnection.getresponse(self)
356
356
357 # general transaction handler to support different ways to handle
357 # general transaction handler to support different ways to handle
358 # HTTPS proxying before and after Python 2.6.3.
358 # HTTPS proxying before and after Python 2.6.3.
359 def _generic_start_transaction(handler, h, req):
359 def _generic_start_transaction(handler, h, req):
360 if hasattr(req, '_tunnel_host') and req._tunnel_host:
360 if hasattr(req, '_tunnel_host') and req._tunnel_host:
361 tunnel_host = req._tunnel_host
361 tunnel_host = req._tunnel_host
362 if tunnel_host[:7] not in ['http://', 'https:/']:
362 if tunnel_host[:7] not in ['http://', 'https:/']:
363 tunnel_host = 'https://' + tunnel_host
363 tunnel_host = 'https://' + tunnel_host
364 new_tunnel = True
364 new_tunnel = True
365 else:
365 else:
366 tunnel_host = req.get_selector()
366 tunnel_host = req.get_selector()
367 new_tunnel = False
367 new_tunnel = False
368
368
369 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
369 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
370 urlparts = urlparse.urlparse(tunnel_host)
370 urlparts = urlparse.urlparse(tunnel_host)
371 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
371 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
372 realhostport = urlparts[1]
372 realhostport = urlparts[1]
373 if realhostport[-1] == ']' or ':' not in realhostport:
373 if realhostport[-1] == ']' or ':' not in realhostport:
374 realhostport += ':443'
374 realhostport += ':443'
375
375
376 h.realhostport = realhostport
376 h.realhostport = realhostport
377 h.headers = req.headers.copy()
377 h.headers = req.headers.copy()
378 h.headers.update(handler.parent.addheaders)
378 h.headers.update(handler.parent.addheaders)
379 return
379 return
380
380
381 h.realhostport = None
381 h.realhostport = None
382 h.headers = None
382 h.headers = None
383
383
384 def _generic_proxytunnel(self):
384 def _generic_proxytunnel(self):
385 proxyheaders = dict(
385 proxyheaders = dict(
386 [(x, self.headers[x]) for x in self.headers
386 [(x, self.headers[x]) for x in self.headers
387 if x.lower().startswith('proxy-')])
387 if x.lower().startswith('proxy-')])
388 self._set_hostport(self.host, self.port)
388 self._set_hostport(self.host, self.port)
389 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
389 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
390 for header in proxyheaders.iteritems():
390 for header in proxyheaders.iteritems():
391 self.send('%s: %s\r\n' % header)
391 self.send('%s: %s\r\n' % header)
392 self.send('\r\n')
392 self.send('\r\n')
393
393
394 # majority of the following code is duplicated from
394 # majority of the following code is duplicated from
395 # httplib.HTTPConnection as there are no adequate places to
395 # httplib.HTTPConnection as there are no adequate places to
396 # override functions to provide the needed functionality
396 # override functions to provide the needed functionality
397 res = self.response_class(self.sock,
397 res = self.response_class(self.sock,
398 strict=self.strict,
398 strict=self.strict,
399 method=self._method)
399 method=self._method)
400
400
401 while True:
401 while True:
402 version, status, reason = res._read_status()
402 version, status, reason = res._read_status()
403 if status != httplib.CONTINUE:
403 if status != httplib.CONTINUE:
404 break
404 break
405 while True:
405 while True:
406 skip = res.fp.readline().strip()
406 skip = res.fp.readline().strip()
407 if not skip:
407 if not skip:
408 break
408 break
409 res.status = status
409 res.status = status
410 res.reason = reason.strip()
410 res.reason = reason.strip()
411
411
412 if res.status == 200:
412 if res.status == 200:
413 while True:
413 while True:
414 line = res.fp.readline()
414 line = res.fp.readline()
415 if line == '\r\n':
415 if line == '\r\n':
416 break
416 break
417 return True
417 return True
418
418
419 if version == 'HTTP/1.0':
419 if version == 'HTTP/1.0':
420 res.version = 10
420 res.version = 10
421 elif version.startswith('HTTP/1.'):
421 elif version.startswith('HTTP/1.'):
422 res.version = 11
422 res.version = 11
423 elif version == 'HTTP/0.9':
423 elif version == 'HTTP/0.9':
424 res.version = 9
424 res.version = 9
425 else:
425 else:
426 raise httplib.UnknownProtocol(version)
426 raise httplib.UnknownProtocol(version)
427
427
428 if res.version == 9:
428 if res.version == 9:
429 res.length = None
429 res.length = None
430 res.chunked = 0
430 res.chunked = 0
431 res.will_close = 1
431 res.will_close = 1
432 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
432 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
433 return False
433 return False
434
434
435 res.msg = httplib.HTTPMessage(res.fp)
435 res.msg = httplib.HTTPMessage(res.fp)
436 res.msg.fp = None
436 res.msg.fp = None
437
437
438 # are we using the chunked-style of transfer encoding?
438 # are we using the chunked-style of transfer encoding?
439 trenc = res.msg.getheader('transfer-encoding')
439 trenc = res.msg.getheader('transfer-encoding')
440 if trenc and trenc.lower() == "chunked":
440 if trenc and trenc.lower() == "chunked":
441 res.chunked = 1
441 res.chunked = 1
442 res.chunk_left = None
442 res.chunk_left = None
443 else:
443 else:
444 res.chunked = 0
444 res.chunked = 0
445
445
446 # will the connection close at the end of the response?
446 # will the connection close at the end of the response?
447 res.will_close = res._check_close()
447 res.will_close = res._check_close()
448
448
449 # do we have a Content-Length?
449 # do we have a Content-Length?
450 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
450 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
451 length = res.msg.getheader('content-length')
451 length = res.msg.getheader('content-length')
452 if length and not res.chunked:
452 if length and not res.chunked:
453 try:
453 try:
454 res.length = int(length)
454 res.length = int(length)
455 except ValueError:
455 except ValueError:
456 res.length = None
456 res.length = None
457 else:
457 else:
458 if res.length < 0: # ignore nonsensical negative lengths
458 if res.length < 0: # ignore nonsensical negative lengths
459 res.length = None
459 res.length = None
460 else:
460 else:
461 res.length = None
461 res.length = None
462
462
463 # does the body have a fixed length? (of zero)
463 # does the body have a fixed length? (of zero)
464 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
464 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
465 100 <= status < 200 or # 1xx codes
465 100 <= status < 200 or # 1xx codes
466 res._method == 'HEAD'):
466 res._method == 'HEAD'):
467 res.length = 0
467 res.length = 0
468
468
469 # if the connection remains open, and we aren't using chunked, and
469 # if the connection remains open, and we aren't using chunked, and
470 # a content-length was not provided, then assume that the connection
470 # a content-length was not provided, then assume that the connection
471 # WILL close.
471 # WILL close.
472 if (not res.will_close and
472 if (not res.will_close and
473 not res.chunked and
473 not res.chunked and
474 res.length is None):
474 res.length is None):
475 res.will_close = 1
475 res.will_close = 1
476
476
477 self.proxyres = res
477 self.proxyres = res
478
478
479 return False
479 return False
480
480
481 class httphandler(keepalive.HTTPHandler):
481 class httphandler(keepalive.HTTPHandler):
482 def http_open(self, req):
482 def http_open(self, req):
483 return self.do_open(httpconnection, req)
483 return self.do_open(httpconnection, req)
484
484
485 def _start_transaction(self, h, req):
485 def _start_transaction(self, h, req):
486 _generic_start_transaction(self, h, req)
486 _generic_start_transaction(self, h, req)
487 return keepalive.HTTPHandler._start_transaction(self, h, req)
487 return keepalive.HTTPHandler._start_transaction(self, h, req)
488
488
489 def _verifycert(cert, hostname):
489 def _verifycert(cert, hostname):
490 '''Verify that cert (in socket.getpeercert() format) matches hostname.
490 '''Verify that cert (in socket.getpeercert() format) matches hostname.
491 CRLs and subjectAltName are not handled.
491 CRLs is not handled.
492
492
493 Returns error message if any problems are found and None on success.
493 Returns error message if any problems are found and None on success.
494 '''
494 '''
495 if not cert:
495 if not cert:
496 return _('no certificate received')
496 return _('no certificate received')
497 dnsname = hostname.lower()
497 dnsname = hostname.lower()
498 def matchdnsname(certname):
499 return (certname == dnsname or
500 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
501
502 san = cert.get('subjectAltName', [])
503 if san:
504 certnames = [value.lower() for key, value in san if key == 'DNS']
505 for name in certnames:
506 if matchdnsname(name):
507 return None
508 return _('certificate is for %s') % ', '.join(certnames)
509
510 # subject is only checked when subjectAltName is empty
498 for s in cert.get('subject', []):
511 for s in cert.get('subject', []):
499 key, value = s[0]
512 key, value = s[0]
500 if key == 'commonName':
513 if key == 'commonName':
501 try:
514 try:
502 # 'subject' entries are unicode
515 # 'subject' entries are unicode
503 certname = value.lower().encode('ascii')
516 certname = value.lower().encode('ascii')
504 except UnicodeEncodeError:
517 except UnicodeEncodeError:
505 return _('IDN in certificate not supported')
518 return _('IDN in certificate not supported')
506 if (certname == dnsname or
519 if matchdnsname(certname):
507 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
508 return None
520 return None
509 return _('certificate is for %s') % certname
521 return _('certificate is for %s') % certname
510 return _('no commonName found in certificate')
522 return _('no commonName or subjectAltName found in certificate')
511
523
512 if has_https:
524 if has_https:
513 class BetterHTTPS(httplib.HTTPSConnection):
525 class BetterHTTPS(httplib.HTTPSConnection):
514 send = keepalive.safesend
526 send = keepalive.safesend
515
527
516 def connect(self):
528 def connect(self):
517 if hasattr(self, 'ui'):
529 if hasattr(self, 'ui'):
518 cacerts = self.ui.config('web', 'cacerts')
530 cacerts = self.ui.config('web', 'cacerts')
519 if cacerts:
531 if cacerts:
520 cacerts = util.expandpath(cacerts)
532 cacerts = util.expandpath(cacerts)
521 else:
533 else:
522 cacerts = None
534 cacerts = None
523
535
524 if cacerts:
536 if cacerts:
525 sock = _create_connection((self.host, self.port))
537 sock = _create_connection((self.host, self.port))
526 self.sock = _ssl_wrap_socket(sock, self.key_file,
538 self.sock = _ssl_wrap_socket(sock, self.key_file,
527 self.cert_file, cert_reqs=CERT_REQUIRED,
539 self.cert_file, cert_reqs=CERT_REQUIRED,
528 ca_certs=cacerts)
540 ca_certs=cacerts)
529 msg = _verifycert(self.sock.getpeercert(), self.host)
541 msg = _verifycert(self.sock.getpeercert(), self.host)
530 if msg:
542 if msg:
531 raise util.Abort(_('%s certificate error: %s') %
543 raise util.Abort(_('%s certificate error: %s') %
532 (self.host, msg))
544 (self.host, msg))
533 self.ui.debug('%s certificate successfully verified\n' %
545 self.ui.debug('%s certificate successfully verified\n' %
534 self.host)
546 self.host)
535 else:
547 else:
536 self.ui.warn(_("warning: %s certificate not verified "
548 self.ui.warn(_("warning: %s certificate not verified "
537 "(check web.cacerts config setting)\n") %
549 "(check web.cacerts config setting)\n") %
538 self.host)
550 self.host)
539 httplib.HTTPSConnection.connect(self)
551 httplib.HTTPSConnection.connect(self)
540
552
541 class httpsconnection(BetterHTTPS):
553 class httpsconnection(BetterHTTPS):
542 response_class = keepalive.HTTPResponse
554 response_class = keepalive.HTTPResponse
543 # must be able to send big bundle as stream.
555 # must be able to send big bundle as stream.
544 send = _gen_sendfile(BetterHTTPS)
556 send = _gen_sendfile(BetterHTTPS)
545 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
557 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
546
558
547 def connect(self):
559 def connect(self):
548 if self.realhostport: # use CONNECT proxy
560 if self.realhostport: # use CONNECT proxy
549 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
561 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
550 self.sock.connect((self.host, self.port))
562 self.sock.connect((self.host, self.port))
551 if _generic_proxytunnel(self):
563 if _generic_proxytunnel(self):
552 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
564 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
553 self.cert_file)
565 self.cert_file)
554 else:
566 else:
555 BetterHTTPS.connect(self)
567 BetterHTTPS.connect(self)
556
568
557 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
569 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
558 def __init__(self, ui):
570 def __init__(self, ui):
559 keepalive.KeepAliveHandler.__init__(self)
571 keepalive.KeepAliveHandler.__init__(self)
560 urllib2.HTTPSHandler.__init__(self)
572 urllib2.HTTPSHandler.__init__(self)
561 self.ui = ui
573 self.ui = ui
562 self.pwmgr = passwordmgr(self.ui)
574 self.pwmgr = passwordmgr(self.ui)
563
575
564 def _start_transaction(self, h, req):
576 def _start_transaction(self, h, req):
565 _generic_start_transaction(self, h, req)
577 _generic_start_transaction(self, h, req)
566 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
578 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
567
579
568 def https_open(self, req):
580 def https_open(self, req):
569 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
581 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
570 return self.do_open(self._makeconnection, req)
582 return self.do_open(self._makeconnection, req)
571
583
572 def _makeconnection(self, host, port=None, *args, **kwargs):
584 def _makeconnection(self, host, port=None, *args, **kwargs):
573 keyfile = None
585 keyfile = None
574 certfile = None
586 certfile = None
575
587
576 if len(args) >= 1: # key_file
588 if len(args) >= 1: # key_file
577 keyfile = args[0]
589 keyfile = args[0]
578 if len(args) >= 2: # cert_file
590 if len(args) >= 2: # cert_file
579 certfile = args[1]
591 certfile = args[1]
580 args = args[2:]
592 args = args[2:]
581
593
582 # if the user has specified different key/cert files in
594 # if the user has specified different key/cert files in
583 # hgrc, we prefer these
595 # hgrc, we prefer these
584 if self.auth and 'key' in self.auth and 'cert' in self.auth:
596 if self.auth and 'key' in self.auth and 'cert' in self.auth:
585 keyfile = self.auth['key']
597 keyfile = self.auth['key']
586 certfile = self.auth['cert']
598 certfile = self.auth['cert']
587
599
588 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
600 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
589 conn.ui = self.ui
601 conn.ui = self.ui
590 return conn
602 return conn
591
603
592 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
604 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
593 def __init__(self, *args, **kwargs):
605 def __init__(self, *args, **kwargs):
594 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
606 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
595 self.retried_req = None
607 self.retried_req = None
596
608
597 def reset_retry_count(self):
609 def reset_retry_count(self):
598 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
610 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
599 # forever. We disable reset_retry_count completely and reset in
611 # forever. We disable reset_retry_count completely and reset in
600 # http_error_auth_reqed instead.
612 # http_error_auth_reqed instead.
601 pass
613 pass
602
614
603 def http_error_auth_reqed(self, auth_header, host, req, headers):
615 def http_error_auth_reqed(self, auth_header, host, req, headers):
604 # Reset the retry counter once for each request.
616 # Reset the retry counter once for each request.
605 if req is not self.retried_req:
617 if req is not self.retried_req:
606 self.retried_req = req
618 self.retried_req = req
607 self.retried = 0
619 self.retried = 0
608 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
620 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
609 # it doesn't know about the auth type requested. This can happen if
621 # it doesn't know about the auth type requested. This can happen if
610 # somebody is using BasicAuth and types a bad password.
622 # somebody is using BasicAuth and types a bad password.
611 try:
623 try:
612 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
624 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
613 self, auth_header, host, req, headers)
625 self, auth_header, host, req, headers)
614 except ValueError, inst:
626 except ValueError, inst:
615 arg = inst.args[0]
627 arg = inst.args[0]
616 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
628 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
617 return
629 return
618 raise
630 raise
619
631
620 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
632 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
621 def __init__(self, *args, **kwargs):
633 def __init__(self, *args, **kwargs):
622 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
634 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
623 self.retried_req = None
635 self.retried_req = None
624
636
625 def reset_retry_count(self):
637 def reset_retry_count(self):
626 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
638 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
627 # forever. We disable reset_retry_count completely and reset in
639 # forever. We disable reset_retry_count completely and reset in
628 # http_error_auth_reqed instead.
640 # http_error_auth_reqed instead.
629 pass
641 pass
630
642
631 def http_error_auth_reqed(self, auth_header, host, req, headers):
643 def http_error_auth_reqed(self, auth_header, host, req, headers):
632 # Reset the retry counter once for each request.
644 # Reset the retry counter once for each request.
633 if req is not self.retried_req:
645 if req is not self.retried_req:
634 self.retried_req = req
646 self.retried_req = req
635 self.retried = 0
647 self.retried = 0
636 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
648 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
637 self, auth_header, host, req, headers)
649 self, auth_header, host, req, headers)
638
650
639 def getauthinfo(path):
651 def getauthinfo(path):
640 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
652 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
641 if not urlpath:
653 if not urlpath:
642 urlpath = '/'
654 urlpath = '/'
643 if scheme != 'file':
655 if scheme != 'file':
644 # XXX: why are we quoting the path again with some smart
656 # XXX: why are we quoting the path again with some smart
645 # heuristic here? Anyway, it cannot be done with file://
657 # heuristic here? Anyway, it cannot be done with file://
646 # urls since path encoding is os/fs dependent (see
658 # urls since path encoding is os/fs dependent (see
647 # urllib.pathname2url() for details).
659 # urllib.pathname2url() for details).
648 urlpath = quotepath(urlpath)
660 urlpath = quotepath(urlpath)
649 host, port, user, passwd = netlocsplit(netloc)
661 host, port, user, passwd = netlocsplit(netloc)
650
662
651 # urllib cannot handle URLs with embedded user or passwd
663 # urllib cannot handle URLs with embedded user or passwd
652 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
664 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
653 urlpath, query, frag))
665 urlpath, query, frag))
654 if user:
666 if user:
655 netloc = host
667 netloc = host
656 if port:
668 if port:
657 netloc += ':' + port
669 netloc += ':' + port
658 # Python < 2.4.3 uses only the netloc to search for a password
670 # Python < 2.4.3 uses only the netloc to search for a password
659 authinfo = (None, (url, netloc), user, passwd or '')
671 authinfo = (None, (url, netloc), user, passwd or '')
660 else:
672 else:
661 authinfo = None
673 authinfo = None
662 return url, authinfo
674 return url, authinfo
663
675
664 handlerfuncs = []
676 handlerfuncs = []
665
677
666 def opener(ui, authinfo=None):
678 def opener(ui, authinfo=None):
667 '''
679 '''
668 construct an opener suitable for urllib2
680 construct an opener suitable for urllib2
669 authinfo will be added to the password manager
681 authinfo will be added to the password manager
670 '''
682 '''
671 handlers = [httphandler()]
683 handlers = [httphandler()]
672 if has_https:
684 if has_https:
673 handlers.append(httpshandler(ui))
685 handlers.append(httpshandler(ui))
674
686
675 handlers.append(proxyhandler(ui))
687 handlers.append(proxyhandler(ui))
676
688
677 passmgr = passwordmgr(ui)
689 passmgr = passwordmgr(ui)
678 if authinfo is not None:
690 if authinfo is not None:
679 passmgr.add_password(*authinfo)
691 passmgr.add_password(*authinfo)
680 user, passwd = authinfo[2:4]
692 user, passwd = authinfo[2:4]
681 ui.debug('http auth: user %s, password %s\n' %
693 ui.debug('http auth: user %s, password %s\n' %
682 (user, passwd and '*' * len(passwd) or 'not set'))
694 (user, passwd and '*' * len(passwd) or 'not set'))
683
695
684 handlers.extend((httpbasicauthhandler(passmgr),
696 handlers.extend((httpbasicauthhandler(passmgr),
685 httpdigestauthhandler(passmgr)))
697 httpdigestauthhandler(passmgr)))
686 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
698 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
687 opener = urllib2.build_opener(*handlers)
699 opener = urllib2.build_opener(*handlers)
688
700
689 # 1.0 here is the _protocol_ version
701 # 1.0 here is the _protocol_ version
690 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
702 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
691 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
703 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
692 return opener
704 return opener
693
705
694 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
706 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
695
707
696 def open(ui, url, data=None):
708 def open(ui, url, data=None):
697 scheme = None
709 scheme = None
698 m = scheme_re.search(url)
710 m = scheme_re.search(url)
699 if m:
711 if m:
700 scheme = m.group(1).lower()
712 scheme = m.group(1).lower()
701 if not scheme:
713 if not scheme:
702 path = util.normpath(os.path.abspath(url))
714 path = util.normpath(os.path.abspath(url))
703 url = 'file://' + urllib.pathname2url(path)
715 url = 'file://' + urllib.pathname2url(path)
704 authinfo = None
716 authinfo = None
705 else:
717 else:
706 url, authinfo = getauthinfo(url)
718 url, authinfo = getauthinfo(url)
707 return opener(ui, authinfo).open(url, data)
719 return opener(ui, authinfo).open(url, data)
@@ -1,42 +1,54 b''
1 import sys
1 import sys
2
2
3 def check(a, b):
3 def check(a, b):
4 if a != b:
4 if a != b:
5 print (a, b)
5 print (a, b)
6
6
7 def cert(cn):
7 def cert(cn):
8 return dict(subject=((('commonName', cn),),))
8 return dict(subject=((('commonName', cn),),))
9
9
10 from mercurial.url import _verifycert
10 from mercurial.url import _verifycert
11
11
12 # Test non-wildcard certificates
12 # Test non-wildcard certificates
13 check(_verifycert(cert('example.com'), 'example.com'),
13 check(_verifycert(cert('example.com'), 'example.com'),
14 None)
14 None)
15 check(_verifycert(cert('example.com'), 'www.example.com'),
15 check(_verifycert(cert('example.com'), 'www.example.com'),
16 'certificate is for example.com')
16 'certificate is for example.com')
17 check(_verifycert(cert('www.example.com'), 'example.com'),
17 check(_verifycert(cert('www.example.com'), 'example.com'),
18 'certificate is for www.example.com')
18 'certificate is for www.example.com')
19
19
20 # Test wildcard certificates
20 # Test wildcard certificates
21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
22 None)
22 None)
23 check(_verifycert(cert('*.example.com'), 'example.com'),
23 check(_verifycert(cert('*.example.com'), 'example.com'),
24 'certificate is for *.example.com')
24 'certificate is for *.example.com')
25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
26 'certificate is for *.example.com')
26 'certificate is for *.example.com')
27
27
28 # Test subjectAltName
29 san_cert = {'subject': ((('commonName', 'example.com'),),),
30 'subjectAltName': (('DNS', '*.example.net'),
31 ('DNS', 'example.net'))}
32 check(_verifycert(san_cert, 'example.net'),
33 None)
34 check(_verifycert(san_cert, 'foo.example.net'),
35 None)
36 # subject is only checked when subjectAltName is empty
37 check(_verifycert(san_cert, 'example.com'),
38 'certificate is for *.example.net, example.net')
39
28 # Avoid some pitfalls
40 # Avoid some pitfalls
29 check(_verifycert(cert('*.foo'), 'foo'),
41 check(_verifycert(cert('*.foo'), 'foo'),
30 'certificate is for *.foo')
42 'certificate is for *.foo')
31 check(_verifycert(cert('*o'), 'foo'),
43 check(_verifycert(cert('*o'), 'foo'),
32 'certificate is for *o')
44 'certificate is for *o')
33
45
34 check(_verifycert({'subject': ()},
46 check(_verifycert({'subject': ()},
35 'example.com'),
47 'example.com'),
36 'no commonName found in certificate')
48 'no commonName or subjectAltName found in certificate')
37 check(_verifycert(None, 'example.com'),
49 check(_verifycert(None, 'example.com'),
38 'no certificate received')
50 'no certificate received')
39
51
40 # Unicode (IDN) certname isn't supported
52 # Unicode (IDN) certname isn't supported
41 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
53 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
42 'IDN in certificate not supported')
54 'IDN in certificate not supported')
General Comments 0
You need to be logged in to leave comments. Login now