##// END OF EJS Templates
url: fix UnicodeDecodeError on certificate verification error...
Yuya Nishihara -
r13248:00411a4f stable
parent child Browse files
Show More
@@ -1,703 +1,707 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 and subjectAltName are 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 for s in cert.get('subject', []):
498 for s in cert.get('subject', []):
499 key, value = s[0]
499 key, value = s[0]
500 if key == 'commonName':
500 if key == 'commonName':
501 certname = value.lower()
501 try:
502 # 'subject' entries are unicode
503 certname = value.lower().encode('ascii')
504 except UnicodeEncodeError:
505 return _('IDN in certificate not supported')
502 if (certname == dnsname or
506 if (certname == dnsname or
503 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
507 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
504 return None
508 return None
505 return _('certificate is for %s') % certname
509 return _('certificate is for %s') % certname
506 return _('no commonName found in certificate')
510 return _('no commonName found in certificate')
507
511
508 if has_https:
512 if has_https:
509 class BetterHTTPS(httplib.HTTPSConnection):
513 class BetterHTTPS(httplib.HTTPSConnection):
510 send = keepalive.safesend
514 send = keepalive.safesend
511
515
512 def connect(self):
516 def connect(self):
513 if hasattr(self, 'ui'):
517 if hasattr(self, 'ui'):
514 cacerts = self.ui.config('web', 'cacerts')
518 cacerts = self.ui.config('web', 'cacerts')
515 if cacerts:
519 if cacerts:
516 cacerts = util.expandpath(cacerts)
520 cacerts = util.expandpath(cacerts)
517 else:
521 else:
518 cacerts = None
522 cacerts = None
519
523
520 if cacerts:
524 if cacerts:
521 sock = _create_connection((self.host, self.port))
525 sock = _create_connection((self.host, self.port))
522 self.sock = _ssl_wrap_socket(sock, self.key_file,
526 self.sock = _ssl_wrap_socket(sock, self.key_file,
523 self.cert_file, cert_reqs=CERT_REQUIRED,
527 self.cert_file, cert_reqs=CERT_REQUIRED,
524 ca_certs=cacerts)
528 ca_certs=cacerts)
525 msg = _verifycert(self.sock.getpeercert(), self.host)
529 msg = _verifycert(self.sock.getpeercert(), self.host)
526 if msg:
530 if msg:
527 raise util.Abort(_('%s certificate error: %s') %
531 raise util.Abort(_('%s certificate error: %s') %
528 (self.host, msg))
532 (self.host, msg))
529 self.ui.debug('%s certificate successfully verified\n' %
533 self.ui.debug('%s certificate successfully verified\n' %
530 self.host)
534 self.host)
531 else:
535 else:
532 self.ui.warn(_("warning: %s certificate not verified "
536 self.ui.warn(_("warning: %s certificate not verified "
533 "(check web.cacerts config setting)\n") %
537 "(check web.cacerts config setting)\n") %
534 self.host)
538 self.host)
535 httplib.HTTPSConnection.connect(self)
539 httplib.HTTPSConnection.connect(self)
536
540
537 class httpsconnection(BetterHTTPS):
541 class httpsconnection(BetterHTTPS):
538 response_class = keepalive.HTTPResponse
542 response_class = keepalive.HTTPResponse
539 # must be able to send big bundle as stream.
543 # must be able to send big bundle as stream.
540 send = _gen_sendfile(BetterHTTPS)
544 send = _gen_sendfile(BetterHTTPS)
541 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
545 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
542
546
543 def connect(self):
547 def connect(self):
544 if self.realhostport: # use CONNECT proxy
548 if self.realhostport: # use CONNECT proxy
545 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
549 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
546 self.sock.connect((self.host, self.port))
550 self.sock.connect((self.host, self.port))
547 if _generic_proxytunnel(self):
551 if _generic_proxytunnel(self):
548 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
552 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
549 self.cert_file)
553 self.cert_file)
550 else:
554 else:
551 BetterHTTPS.connect(self)
555 BetterHTTPS.connect(self)
552
556
553 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
557 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
554 def __init__(self, ui):
558 def __init__(self, ui):
555 keepalive.KeepAliveHandler.__init__(self)
559 keepalive.KeepAliveHandler.__init__(self)
556 urllib2.HTTPSHandler.__init__(self)
560 urllib2.HTTPSHandler.__init__(self)
557 self.ui = ui
561 self.ui = ui
558 self.pwmgr = passwordmgr(self.ui)
562 self.pwmgr = passwordmgr(self.ui)
559
563
560 def _start_transaction(self, h, req):
564 def _start_transaction(self, h, req):
561 _generic_start_transaction(self, h, req)
565 _generic_start_transaction(self, h, req)
562 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
566 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
563
567
564 def https_open(self, req):
568 def https_open(self, req):
565 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
569 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
566 return self.do_open(self._makeconnection, req)
570 return self.do_open(self._makeconnection, req)
567
571
568 def _makeconnection(self, host, port=None, *args, **kwargs):
572 def _makeconnection(self, host, port=None, *args, **kwargs):
569 keyfile = None
573 keyfile = None
570 certfile = None
574 certfile = None
571
575
572 if len(args) >= 1: # key_file
576 if len(args) >= 1: # key_file
573 keyfile = args[0]
577 keyfile = args[0]
574 if len(args) >= 2: # cert_file
578 if len(args) >= 2: # cert_file
575 certfile = args[1]
579 certfile = args[1]
576 args = args[2:]
580 args = args[2:]
577
581
578 # if the user has specified different key/cert files in
582 # if the user has specified different key/cert files in
579 # hgrc, we prefer these
583 # hgrc, we prefer these
580 if self.auth and 'key' in self.auth and 'cert' in self.auth:
584 if self.auth and 'key' in self.auth and 'cert' in self.auth:
581 keyfile = self.auth['key']
585 keyfile = self.auth['key']
582 certfile = self.auth['cert']
586 certfile = self.auth['cert']
583
587
584 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
588 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
585 conn.ui = self.ui
589 conn.ui = self.ui
586 return conn
590 return conn
587
591
588 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
592 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
589 def __init__(self, *args, **kwargs):
593 def __init__(self, *args, **kwargs):
590 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
594 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
591 self.retried_req = None
595 self.retried_req = None
592
596
593 def reset_retry_count(self):
597 def reset_retry_count(self):
594 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
598 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
595 # forever. We disable reset_retry_count completely and reset in
599 # forever. We disable reset_retry_count completely and reset in
596 # http_error_auth_reqed instead.
600 # http_error_auth_reqed instead.
597 pass
601 pass
598
602
599 def http_error_auth_reqed(self, auth_header, host, req, headers):
603 def http_error_auth_reqed(self, auth_header, host, req, headers):
600 # Reset the retry counter once for each request.
604 # Reset the retry counter once for each request.
601 if req is not self.retried_req:
605 if req is not self.retried_req:
602 self.retried_req = req
606 self.retried_req = req
603 self.retried = 0
607 self.retried = 0
604 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
608 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
605 # it doesn't know about the auth type requested. This can happen if
609 # it doesn't know about the auth type requested. This can happen if
606 # somebody is using BasicAuth and types a bad password.
610 # somebody is using BasicAuth and types a bad password.
607 try:
611 try:
608 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
612 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
609 self, auth_header, host, req, headers)
613 self, auth_header, host, req, headers)
610 except ValueError, inst:
614 except ValueError, inst:
611 arg = inst.args[0]
615 arg = inst.args[0]
612 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
616 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
613 return
617 return
614 raise
618 raise
615
619
616 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
620 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
617 def __init__(self, *args, **kwargs):
621 def __init__(self, *args, **kwargs):
618 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
622 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
619 self.retried_req = None
623 self.retried_req = None
620
624
621 def reset_retry_count(self):
625 def reset_retry_count(self):
622 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
626 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
623 # forever. We disable reset_retry_count completely and reset in
627 # forever. We disable reset_retry_count completely and reset in
624 # http_error_auth_reqed instead.
628 # http_error_auth_reqed instead.
625 pass
629 pass
626
630
627 def http_error_auth_reqed(self, auth_header, host, req, headers):
631 def http_error_auth_reqed(self, auth_header, host, req, headers):
628 # Reset the retry counter once for each request.
632 # Reset the retry counter once for each request.
629 if req is not self.retried_req:
633 if req is not self.retried_req:
630 self.retried_req = req
634 self.retried_req = req
631 self.retried = 0
635 self.retried = 0
632 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
636 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
633 self, auth_header, host, req, headers)
637 self, auth_header, host, req, headers)
634
638
635 def getauthinfo(path):
639 def getauthinfo(path):
636 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
640 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
637 if not urlpath:
641 if not urlpath:
638 urlpath = '/'
642 urlpath = '/'
639 if scheme != 'file':
643 if scheme != 'file':
640 # XXX: why are we quoting the path again with some smart
644 # XXX: why are we quoting the path again with some smart
641 # heuristic here? Anyway, it cannot be done with file://
645 # heuristic here? Anyway, it cannot be done with file://
642 # urls since path encoding is os/fs dependent (see
646 # urls since path encoding is os/fs dependent (see
643 # urllib.pathname2url() for details).
647 # urllib.pathname2url() for details).
644 urlpath = quotepath(urlpath)
648 urlpath = quotepath(urlpath)
645 host, port, user, passwd = netlocsplit(netloc)
649 host, port, user, passwd = netlocsplit(netloc)
646
650
647 # urllib cannot handle URLs with embedded user or passwd
651 # urllib cannot handle URLs with embedded user or passwd
648 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
652 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
649 urlpath, query, frag))
653 urlpath, query, frag))
650 if user:
654 if user:
651 netloc = host
655 netloc = host
652 if port:
656 if port:
653 netloc += ':' + port
657 netloc += ':' + port
654 # Python < 2.4.3 uses only the netloc to search for a password
658 # Python < 2.4.3 uses only the netloc to search for a password
655 authinfo = (None, (url, netloc), user, passwd or '')
659 authinfo = (None, (url, netloc), user, passwd or '')
656 else:
660 else:
657 authinfo = None
661 authinfo = None
658 return url, authinfo
662 return url, authinfo
659
663
660 handlerfuncs = []
664 handlerfuncs = []
661
665
662 def opener(ui, authinfo=None):
666 def opener(ui, authinfo=None):
663 '''
667 '''
664 construct an opener suitable for urllib2
668 construct an opener suitable for urllib2
665 authinfo will be added to the password manager
669 authinfo will be added to the password manager
666 '''
670 '''
667 handlers = [httphandler()]
671 handlers = [httphandler()]
668 if has_https:
672 if has_https:
669 handlers.append(httpshandler(ui))
673 handlers.append(httpshandler(ui))
670
674
671 handlers.append(proxyhandler(ui))
675 handlers.append(proxyhandler(ui))
672
676
673 passmgr = passwordmgr(ui)
677 passmgr = passwordmgr(ui)
674 if authinfo is not None:
678 if authinfo is not None:
675 passmgr.add_password(*authinfo)
679 passmgr.add_password(*authinfo)
676 user, passwd = authinfo[2:4]
680 user, passwd = authinfo[2:4]
677 ui.debug('http auth: user %s, password %s\n' %
681 ui.debug('http auth: user %s, password %s\n' %
678 (user, passwd and '*' * len(passwd) or 'not set'))
682 (user, passwd and '*' * len(passwd) or 'not set'))
679
683
680 handlers.extend((httpbasicauthhandler(passmgr),
684 handlers.extend((httpbasicauthhandler(passmgr),
681 httpdigestauthhandler(passmgr)))
685 httpdigestauthhandler(passmgr)))
682 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
686 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
683 opener = urllib2.build_opener(*handlers)
687 opener = urllib2.build_opener(*handlers)
684
688
685 # 1.0 here is the _protocol_ version
689 # 1.0 here is the _protocol_ version
686 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
690 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
687 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
691 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
688 return opener
692 return opener
689
693
690 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
694 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
691
695
692 def open(ui, url, data=None):
696 def open(ui, url, data=None):
693 scheme = None
697 scheme = None
694 m = scheme_re.search(url)
698 m = scheme_re.search(url)
695 if m:
699 if m:
696 scheme = m.group(1).lower()
700 scheme = m.group(1).lower()
697 if not scheme:
701 if not scheme:
698 path = util.normpath(os.path.abspath(url))
702 path = util.normpath(os.path.abspath(url))
699 url = 'file://' + urllib.pathname2url(path)
703 url = 'file://' + urllib.pathname2url(path)
700 authinfo = None
704 authinfo = None
701 else:
705 else:
702 url, authinfo = getauthinfo(url)
706 url, authinfo = getauthinfo(url)
703 return opener(ui, authinfo).open(url, data)
707 return opener(ui, authinfo).open(url, data)
@@ -1,38 +1,42 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 # Avoid some pitfalls
28 # Avoid some pitfalls
29 check(_verifycert(cert('*.foo'), 'foo'),
29 check(_verifycert(cert('*.foo'), 'foo'),
30 'certificate is for *.foo')
30 'certificate is for *.foo')
31 check(_verifycert(cert('*o'), 'foo'),
31 check(_verifycert(cert('*o'), 'foo'),
32 'certificate is for *o')
32 'certificate is for *o')
33
33
34 check(_verifycert({'subject': ()},
34 check(_verifycert({'subject': ()},
35 'example.com'),
35 'example.com'),
36 'no commonName found in certificate')
36 'no commonName found in certificate')
37 check(_verifycert(None, 'example.com'),
37 check(_verifycert(None, 'example.com'),
38 'no certificate received')
38 'no certificate received')
39
40 # Unicode (IDN) certname isn't supported
41 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
42 'IDN in certificate not supported')
General Comments 0
You need to be logged in to leave comments. Login now