##// END OF EJS Templates
merge with stable
Mads Kiilerich -
r13250:1a4330e3 merge default
parent child Browse files
Show More
@@ -1,721 +1,737 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, ui, *args, **kwargs):
261 def __init__(self, ui, *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.ui = ui
265 self.ui = ui
266 self._data = __builtin__.open(*args, **kwargs)
266 self._data = __builtin__.open(*args, **kwargs)
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 self._len = os.fstat(self._data.fileno()).st_size
270 self._len = os.fstat(self._data.fileno()).st_size
271 self._pos = 0
271 self._pos = 0
272 self._total = len(self) / 1024 * 2
272 self._total = len(self) / 1024 * 2
273
273
274 def read(self, *args, **kwargs):
274 def read(self, *args, **kwargs):
275 try:
275 try:
276 ret = self._data.read(*args, **kwargs)
276 ret = self._data.read(*args, **kwargs)
277 except EOFError:
277 except EOFError:
278 self.ui.progress(_('sending'), None)
278 self.ui.progress(_('sending'), None)
279 self._pos += len(ret)
279 self._pos += len(ret)
280 # We pass double the max for total because we currently have
280 # We pass double the max for total because we currently have
281 # to send the bundle twice in the case of a server that
281 # to send the bundle twice in the case of a server that
282 # requires authentication. Since we can't know until we try
282 # requires authentication. Since we can't know until we try
283 # once whether authentication will be required, just lie to
283 # once whether authentication will be required, just lie to
284 # the user and maybe the push succeeds suddenly at 50%.
284 # the user and maybe the push succeeds suddenly at 50%.
285 self.ui.progress(_('sending'), self._pos / 1024,
285 self.ui.progress(_('sending'), self._pos / 1024,
286 unit=_('kb'), total=self._total)
286 unit=_('kb'), total=self._total)
287 return ret
287 return ret
288
288
289 def __len__(self):
289 def __len__(self):
290 return self._len
290 return self._len
291
291
292 def _gen_sendfile(connection):
292 def _gen_sendfile(connection):
293 def _sendfile(self, data):
293 def _sendfile(self, data):
294 # send a file
294 # send a file
295 if isinstance(data, httpsendfile):
295 if isinstance(data, httpsendfile):
296 # if auth required, some data sent twice, so rewind here
296 # if auth required, some data sent twice, so rewind here
297 data.seek(0)
297 data.seek(0)
298 for chunk in util.filechunkiter(data):
298 for chunk in util.filechunkiter(data):
299 connection.send(self, chunk)
299 connection.send(self, chunk)
300 else:
300 else:
301 connection.send(self, data)
301 connection.send(self, data)
302 return _sendfile
302 return _sendfile
303
303
304 has_https = hasattr(urllib2, 'HTTPSHandler')
304 has_https = hasattr(urllib2, 'HTTPSHandler')
305 if has_https:
305 if has_https:
306 try:
306 try:
307 # avoid using deprecated/broken FakeSocket in python 2.6
307 # avoid using deprecated/broken FakeSocket in python 2.6
308 import ssl
308 import ssl
309 _ssl_wrap_socket = ssl.wrap_socket
309 _ssl_wrap_socket = ssl.wrap_socket
310 CERT_REQUIRED = ssl.CERT_REQUIRED
310 CERT_REQUIRED = ssl.CERT_REQUIRED
311 except ImportError:
311 except ImportError:
312 CERT_REQUIRED = 2
312 CERT_REQUIRED = 2
313
313
314 def _ssl_wrap_socket(sock, key_file, cert_file,
314 def _ssl_wrap_socket(sock, key_file, cert_file,
315 cert_reqs=CERT_REQUIRED, ca_certs=None):
315 cert_reqs=CERT_REQUIRED, ca_certs=None):
316 if ca_certs:
316 if ca_certs:
317 raise util.Abort(_(
317 raise util.Abort(_(
318 'certificate checking requires Python 2.6'))
318 'certificate checking requires Python 2.6'))
319
319
320 ssl = socket.ssl(sock, key_file, cert_file)
320 ssl = socket.ssl(sock, key_file, cert_file)
321 return httplib.FakeSocket(sock, ssl)
321 return httplib.FakeSocket(sock, ssl)
322
322
323 try:
323 try:
324 _create_connection = socket.create_connection
324 _create_connection = socket.create_connection
325 except AttributeError:
325 except AttributeError:
326 _GLOBAL_DEFAULT_TIMEOUT = object()
326 _GLOBAL_DEFAULT_TIMEOUT = object()
327
327
328 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
328 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
329 source_address=None):
329 source_address=None):
330 # lifted from Python 2.6
330 # lifted from Python 2.6
331
331
332 msg = "getaddrinfo returns an empty list"
332 msg = "getaddrinfo returns an empty list"
333 host, port = address
333 host, port = address
334 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
334 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
335 af, socktype, proto, canonname, sa = res
335 af, socktype, proto, canonname, sa = res
336 sock = None
336 sock = None
337 try:
337 try:
338 sock = socket.socket(af, socktype, proto)
338 sock = socket.socket(af, socktype, proto)
339 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
339 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
340 sock.settimeout(timeout)
340 sock.settimeout(timeout)
341 if source_address:
341 if source_address:
342 sock.bind(source_address)
342 sock.bind(source_address)
343 sock.connect(sa)
343 sock.connect(sa)
344 return sock
344 return sock
345
345
346 except socket.error, msg:
346 except socket.error, msg:
347 if sock is not None:
347 if sock is not None:
348 sock.close()
348 sock.close()
349
349
350 raise socket.error, msg
350 raise socket.error, msg
351
351
352 class httpconnection(keepalive.HTTPConnection):
352 class httpconnection(keepalive.HTTPConnection):
353 # must be able to send big bundle as stream.
353 # must be able to send big bundle as stream.
354 send = _gen_sendfile(keepalive.HTTPConnection)
354 send = _gen_sendfile(keepalive.HTTPConnection)
355
355
356 def connect(self):
356 def connect(self):
357 if has_https and self.realhostport: # use CONNECT proxy
357 if has_https and self.realhostport: # use CONNECT proxy
358 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
358 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
359 self.sock.connect((self.host, self.port))
359 self.sock.connect((self.host, self.port))
360 if _generic_proxytunnel(self):
360 if _generic_proxytunnel(self):
361 # we do not support client x509 certificates
361 # we do not support client x509 certificates
362 self.sock = _ssl_wrap_socket(self.sock, None, None)
362 self.sock = _ssl_wrap_socket(self.sock, None, None)
363 else:
363 else:
364 keepalive.HTTPConnection.connect(self)
364 keepalive.HTTPConnection.connect(self)
365
365
366 def getresponse(self):
366 def getresponse(self):
367 proxyres = getattr(self, 'proxyres', None)
367 proxyres = getattr(self, 'proxyres', None)
368 if proxyres:
368 if proxyres:
369 if proxyres.will_close:
369 if proxyres.will_close:
370 self.close()
370 self.close()
371 self.proxyres = None
371 self.proxyres = None
372 return proxyres
372 return proxyres
373 return keepalive.HTTPConnection.getresponse(self)
373 return keepalive.HTTPConnection.getresponse(self)
374
374
375 # general transaction handler to support different ways to handle
375 # general transaction handler to support different ways to handle
376 # HTTPS proxying before and after Python 2.6.3.
376 # HTTPS proxying before and after Python 2.6.3.
377 def _generic_start_transaction(handler, h, req):
377 def _generic_start_transaction(handler, h, req):
378 if hasattr(req, '_tunnel_host') and req._tunnel_host:
378 if hasattr(req, '_tunnel_host') and req._tunnel_host:
379 tunnel_host = req._tunnel_host
379 tunnel_host = req._tunnel_host
380 if tunnel_host[:7] not in ['http://', 'https:/']:
380 if tunnel_host[:7] not in ['http://', 'https:/']:
381 tunnel_host = 'https://' + tunnel_host
381 tunnel_host = 'https://' + tunnel_host
382 new_tunnel = True
382 new_tunnel = True
383 else:
383 else:
384 tunnel_host = req.get_selector()
384 tunnel_host = req.get_selector()
385 new_tunnel = False
385 new_tunnel = False
386
386
387 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
387 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
388 urlparts = urlparse.urlparse(tunnel_host)
388 urlparts = urlparse.urlparse(tunnel_host)
389 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
389 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
390 realhostport = urlparts[1]
390 realhostport = urlparts[1]
391 if realhostport[-1] == ']' or ':' not in realhostport:
391 if realhostport[-1] == ']' or ':' not in realhostport:
392 realhostport += ':443'
392 realhostport += ':443'
393
393
394 h.realhostport = realhostport
394 h.realhostport = realhostport
395 h.headers = req.headers.copy()
395 h.headers = req.headers.copy()
396 h.headers.update(handler.parent.addheaders)
396 h.headers.update(handler.parent.addheaders)
397 return
397 return
398
398
399 h.realhostport = None
399 h.realhostport = None
400 h.headers = None
400 h.headers = None
401
401
402 def _generic_proxytunnel(self):
402 def _generic_proxytunnel(self):
403 proxyheaders = dict(
403 proxyheaders = dict(
404 [(x, self.headers[x]) for x in self.headers
404 [(x, self.headers[x]) for x in self.headers
405 if x.lower().startswith('proxy-')])
405 if x.lower().startswith('proxy-')])
406 self._set_hostport(self.host, self.port)
406 self._set_hostport(self.host, self.port)
407 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
407 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
408 for header in proxyheaders.iteritems():
408 for header in proxyheaders.iteritems():
409 self.send('%s: %s\r\n' % header)
409 self.send('%s: %s\r\n' % header)
410 self.send('\r\n')
410 self.send('\r\n')
411
411
412 # majority of the following code is duplicated from
412 # majority of the following code is duplicated from
413 # httplib.HTTPConnection as there are no adequate places to
413 # httplib.HTTPConnection as there are no adequate places to
414 # override functions to provide the needed functionality
414 # override functions to provide the needed functionality
415 res = self.response_class(self.sock,
415 res = self.response_class(self.sock,
416 strict=self.strict,
416 strict=self.strict,
417 method=self._method)
417 method=self._method)
418
418
419 while True:
419 while True:
420 version, status, reason = res._read_status()
420 version, status, reason = res._read_status()
421 if status != httplib.CONTINUE:
421 if status != httplib.CONTINUE:
422 break
422 break
423 while True:
423 while True:
424 skip = res.fp.readline().strip()
424 skip = res.fp.readline().strip()
425 if not skip:
425 if not skip:
426 break
426 break
427 res.status = status
427 res.status = status
428 res.reason = reason.strip()
428 res.reason = reason.strip()
429
429
430 if res.status == 200:
430 if res.status == 200:
431 while True:
431 while True:
432 line = res.fp.readline()
432 line = res.fp.readline()
433 if line == '\r\n':
433 if line == '\r\n':
434 break
434 break
435 return True
435 return True
436
436
437 if version == 'HTTP/1.0':
437 if version == 'HTTP/1.0':
438 res.version = 10
438 res.version = 10
439 elif version.startswith('HTTP/1.'):
439 elif version.startswith('HTTP/1.'):
440 res.version = 11
440 res.version = 11
441 elif version == 'HTTP/0.9':
441 elif version == 'HTTP/0.9':
442 res.version = 9
442 res.version = 9
443 else:
443 else:
444 raise httplib.UnknownProtocol(version)
444 raise httplib.UnknownProtocol(version)
445
445
446 if res.version == 9:
446 if res.version == 9:
447 res.length = None
447 res.length = None
448 res.chunked = 0
448 res.chunked = 0
449 res.will_close = 1
449 res.will_close = 1
450 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
450 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
451 return False
451 return False
452
452
453 res.msg = httplib.HTTPMessage(res.fp)
453 res.msg = httplib.HTTPMessage(res.fp)
454 res.msg.fp = None
454 res.msg.fp = None
455
455
456 # are we using the chunked-style of transfer encoding?
456 # are we using the chunked-style of transfer encoding?
457 trenc = res.msg.getheader('transfer-encoding')
457 trenc = res.msg.getheader('transfer-encoding')
458 if trenc and trenc.lower() == "chunked":
458 if trenc and trenc.lower() == "chunked":
459 res.chunked = 1
459 res.chunked = 1
460 res.chunk_left = None
460 res.chunk_left = None
461 else:
461 else:
462 res.chunked = 0
462 res.chunked = 0
463
463
464 # will the connection close at the end of the response?
464 # will the connection close at the end of the response?
465 res.will_close = res._check_close()
465 res.will_close = res._check_close()
466
466
467 # do we have a Content-Length?
467 # do we have a Content-Length?
468 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
468 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
469 length = res.msg.getheader('content-length')
469 length = res.msg.getheader('content-length')
470 if length and not res.chunked:
470 if length and not res.chunked:
471 try:
471 try:
472 res.length = int(length)
472 res.length = int(length)
473 except ValueError:
473 except ValueError:
474 res.length = None
474 res.length = None
475 else:
475 else:
476 if res.length < 0: # ignore nonsensical negative lengths
476 if res.length < 0: # ignore nonsensical negative lengths
477 res.length = None
477 res.length = None
478 else:
478 else:
479 res.length = None
479 res.length = None
480
480
481 # does the body have a fixed length? (of zero)
481 # does the body have a fixed length? (of zero)
482 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
482 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
483 100 <= status < 200 or # 1xx codes
483 100 <= status < 200 or # 1xx codes
484 res._method == 'HEAD'):
484 res._method == 'HEAD'):
485 res.length = 0
485 res.length = 0
486
486
487 # if the connection remains open, and we aren't using chunked, and
487 # if the connection remains open, and we aren't using chunked, and
488 # a content-length was not provided, then assume that the connection
488 # a content-length was not provided, then assume that the connection
489 # WILL close.
489 # WILL close.
490 if (not res.will_close and
490 if (not res.will_close and
491 not res.chunked and
491 not res.chunked and
492 res.length is None):
492 res.length is None):
493 res.will_close = 1
493 res.will_close = 1
494
494
495 self.proxyres = res
495 self.proxyres = res
496
496
497 return False
497 return False
498
498
499 class httphandler(keepalive.HTTPHandler):
499 class httphandler(keepalive.HTTPHandler):
500 def http_open(self, req):
500 def http_open(self, req):
501 return self.do_open(httpconnection, req)
501 return self.do_open(httpconnection, req)
502
502
503 def _start_transaction(self, h, req):
503 def _start_transaction(self, h, req):
504 _generic_start_transaction(self, h, req)
504 _generic_start_transaction(self, h, req)
505 return keepalive.HTTPHandler._start_transaction(self, h, req)
505 return keepalive.HTTPHandler._start_transaction(self, h, req)
506
506
507 def _verifycert(cert, hostname):
507 def _verifycert(cert, hostname):
508 '''Verify that cert (in socket.getpeercert() format) matches hostname.
508 '''Verify that cert (in socket.getpeercert() format) matches hostname.
509 CRLs and subjectAltName are not handled.
509 CRLs is not handled.
510
510
511 Returns error message if any problems are found and None on success.
511 Returns error message if any problems are found and None on success.
512 '''
512 '''
513 if not cert:
513 if not cert:
514 return _('no certificate received')
514 return _('no certificate received')
515 dnsname = hostname.lower()
515 dnsname = hostname.lower()
516 def matchdnsname(certname):
517 return (certname == dnsname or
518 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
519
520 san = cert.get('subjectAltName', [])
521 if san:
522 certnames = [value.lower() for key, value in san if key == 'DNS']
523 for name in certnames:
524 if matchdnsname(name):
525 return None
526 return _('certificate is for %s') % ', '.join(certnames)
527
528 # subject is only checked when subjectAltName is empty
516 for s in cert.get('subject', []):
529 for s in cert.get('subject', []):
517 key, value = s[0]
530 key, value = s[0]
518 if key == 'commonName':
531 if key == 'commonName':
519 certname = value.lower()
532 try:
520 if (certname == dnsname or
533 # 'subject' entries are unicode
521 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
534 certname = value.lower().encode('ascii')
535 except UnicodeEncodeError:
536 return _('IDN in certificate not supported')
537 if matchdnsname(certname):
522 return None
538 return None
523 return _('certificate is for %s') % certname
539 return _('certificate is for %s') % certname
524 return _('no commonName found in certificate')
540 return _('no commonName or subjectAltName found in certificate')
525
541
526 if has_https:
542 if has_https:
527 class BetterHTTPS(httplib.HTTPSConnection):
543 class BetterHTTPS(httplib.HTTPSConnection):
528 send = keepalive.safesend
544 send = keepalive.safesend
529
545
530 def connect(self):
546 def connect(self):
531 if hasattr(self, 'ui'):
547 if hasattr(self, 'ui'):
532 cacerts = self.ui.config('web', 'cacerts')
548 cacerts = self.ui.config('web', 'cacerts')
533 if cacerts:
549 if cacerts:
534 cacerts = util.expandpath(cacerts)
550 cacerts = util.expandpath(cacerts)
535 else:
551 else:
536 cacerts = None
552 cacerts = None
537
553
538 if cacerts:
554 if cacerts:
539 sock = _create_connection((self.host, self.port))
555 sock = _create_connection((self.host, self.port))
540 self.sock = _ssl_wrap_socket(sock, self.key_file,
556 self.sock = _ssl_wrap_socket(sock, self.key_file,
541 self.cert_file, cert_reqs=CERT_REQUIRED,
557 self.cert_file, cert_reqs=CERT_REQUIRED,
542 ca_certs=cacerts)
558 ca_certs=cacerts)
543 msg = _verifycert(self.sock.getpeercert(), self.host)
559 msg = _verifycert(self.sock.getpeercert(), self.host)
544 if msg:
560 if msg:
545 raise util.Abort(_('%s certificate error: %s') %
561 raise util.Abort(_('%s certificate error: %s') %
546 (self.host, msg))
562 (self.host, msg))
547 self.ui.debug('%s certificate successfully verified\n' %
563 self.ui.debug('%s certificate successfully verified\n' %
548 self.host)
564 self.host)
549 else:
565 else:
550 self.ui.warn(_("warning: %s certificate not verified "
566 self.ui.warn(_("warning: %s certificate not verified "
551 "(check web.cacerts config setting)\n") %
567 "(check web.cacerts config setting)\n") %
552 self.host)
568 self.host)
553 httplib.HTTPSConnection.connect(self)
569 httplib.HTTPSConnection.connect(self)
554
570
555 class httpsconnection(BetterHTTPS):
571 class httpsconnection(BetterHTTPS):
556 response_class = keepalive.HTTPResponse
572 response_class = keepalive.HTTPResponse
557 # must be able to send big bundle as stream.
573 # must be able to send big bundle as stream.
558 send = _gen_sendfile(BetterHTTPS)
574 send = _gen_sendfile(BetterHTTPS)
559 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
575 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
560
576
561 def connect(self):
577 def connect(self):
562 if self.realhostport: # use CONNECT proxy
578 if self.realhostport: # use CONNECT proxy
563 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
579 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
564 self.sock.connect((self.host, self.port))
580 self.sock.connect((self.host, self.port))
565 if _generic_proxytunnel(self):
581 if _generic_proxytunnel(self):
566 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
582 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
567 self.cert_file)
583 self.cert_file)
568 else:
584 else:
569 BetterHTTPS.connect(self)
585 BetterHTTPS.connect(self)
570
586
571 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
587 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
572 def __init__(self, ui):
588 def __init__(self, ui):
573 keepalive.KeepAliveHandler.__init__(self)
589 keepalive.KeepAliveHandler.__init__(self)
574 urllib2.HTTPSHandler.__init__(self)
590 urllib2.HTTPSHandler.__init__(self)
575 self.ui = ui
591 self.ui = ui
576 self.pwmgr = passwordmgr(self.ui)
592 self.pwmgr = passwordmgr(self.ui)
577
593
578 def _start_transaction(self, h, req):
594 def _start_transaction(self, h, req):
579 _generic_start_transaction(self, h, req)
595 _generic_start_transaction(self, h, req)
580 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
596 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
581
597
582 def https_open(self, req):
598 def https_open(self, req):
583 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
599 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
584 return self.do_open(self._makeconnection, req)
600 return self.do_open(self._makeconnection, req)
585
601
586 def _makeconnection(self, host, port=None, *args, **kwargs):
602 def _makeconnection(self, host, port=None, *args, **kwargs):
587 keyfile = None
603 keyfile = None
588 certfile = None
604 certfile = None
589
605
590 if len(args) >= 1: # key_file
606 if len(args) >= 1: # key_file
591 keyfile = args[0]
607 keyfile = args[0]
592 if len(args) >= 2: # cert_file
608 if len(args) >= 2: # cert_file
593 certfile = args[1]
609 certfile = args[1]
594 args = args[2:]
610 args = args[2:]
595
611
596 # if the user has specified different key/cert files in
612 # if the user has specified different key/cert files in
597 # hgrc, we prefer these
613 # hgrc, we prefer these
598 if self.auth and 'key' in self.auth and 'cert' in self.auth:
614 if self.auth and 'key' in self.auth and 'cert' in self.auth:
599 keyfile = self.auth['key']
615 keyfile = self.auth['key']
600 certfile = self.auth['cert']
616 certfile = self.auth['cert']
601
617
602 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
618 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
603 conn.ui = self.ui
619 conn.ui = self.ui
604 return conn
620 return conn
605
621
606 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
622 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
607 def __init__(self, *args, **kwargs):
623 def __init__(self, *args, **kwargs):
608 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
624 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
609 self.retried_req = None
625 self.retried_req = None
610
626
611 def reset_retry_count(self):
627 def reset_retry_count(self):
612 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
628 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
613 # forever. We disable reset_retry_count completely and reset in
629 # forever. We disable reset_retry_count completely and reset in
614 # http_error_auth_reqed instead.
630 # http_error_auth_reqed instead.
615 pass
631 pass
616
632
617 def http_error_auth_reqed(self, auth_header, host, req, headers):
633 def http_error_auth_reqed(self, auth_header, host, req, headers):
618 # Reset the retry counter once for each request.
634 # Reset the retry counter once for each request.
619 if req is not self.retried_req:
635 if req is not self.retried_req:
620 self.retried_req = req
636 self.retried_req = req
621 self.retried = 0
637 self.retried = 0
622 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
638 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
623 # it doesn't know about the auth type requested. This can happen if
639 # it doesn't know about the auth type requested. This can happen if
624 # somebody is using BasicAuth and types a bad password.
640 # somebody is using BasicAuth and types a bad password.
625 try:
641 try:
626 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
642 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
627 self, auth_header, host, req, headers)
643 self, auth_header, host, req, headers)
628 except ValueError, inst:
644 except ValueError, inst:
629 arg = inst.args[0]
645 arg = inst.args[0]
630 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
646 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
631 return
647 return
632 raise
648 raise
633
649
634 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
650 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
635 def __init__(self, *args, **kwargs):
651 def __init__(self, *args, **kwargs):
636 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
652 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
637 self.retried_req = None
653 self.retried_req = None
638
654
639 def reset_retry_count(self):
655 def reset_retry_count(self):
640 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
656 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
641 # forever. We disable reset_retry_count completely and reset in
657 # forever. We disable reset_retry_count completely and reset in
642 # http_error_auth_reqed instead.
658 # http_error_auth_reqed instead.
643 pass
659 pass
644
660
645 def http_error_auth_reqed(self, auth_header, host, req, headers):
661 def http_error_auth_reqed(self, auth_header, host, req, headers):
646 # Reset the retry counter once for each request.
662 # Reset the retry counter once for each request.
647 if req is not self.retried_req:
663 if req is not self.retried_req:
648 self.retried_req = req
664 self.retried_req = req
649 self.retried = 0
665 self.retried = 0
650 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
666 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
651 self, auth_header, host, req, headers)
667 self, auth_header, host, req, headers)
652
668
653 def getauthinfo(path):
669 def getauthinfo(path):
654 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
670 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
655 if not urlpath:
671 if not urlpath:
656 urlpath = '/'
672 urlpath = '/'
657 if scheme != 'file':
673 if scheme != 'file':
658 # XXX: why are we quoting the path again with some smart
674 # XXX: why are we quoting the path again with some smart
659 # heuristic here? Anyway, it cannot be done with file://
675 # heuristic here? Anyway, it cannot be done with file://
660 # urls since path encoding is os/fs dependent (see
676 # urls since path encoding is os/fs dependent (see
661 # urllib.pathname2url() for details).
677 # urllib.pathname2url() for details).
662 urlpath = quotepath(urlpath)
678 urlpath = quotepath(urlpath)
663 host, port, user, passwd = netlocsplit(netloc)
679 host, port, user, passwd = netlocsplit(netloc)
664
680
665 # urllib cannot handle URLs with embedded user or passwd
681 # urllib cannot handle URLs with embedded user or passwd
666 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
682 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
667 urlpath, query, frag))
683 urlpath, query, frag))
668 if user:
684 if user:
669 netloc = host
685 netloc = host
670 if port:
686 if port:
671 netloc += ':' + port
687 netloc += ':' + port
672 # Python < 2.4.3 uses only the netloc to search for a password
688 # Python < 2.4.3 uses only the netloc to search for a password
673 authinfo = (None, (url, netloc), user, passwd or '')
689 authinfo = (None, (url, netloc), user, passwd or '')
674 else:
690 else:
675 authinfo = None
691 authinfo = None
676 return url, authinfo
692 return url, authinfo
677
693
678 handlerfuncs = []
694 handlerfuncs = []
679
695
680 def opener(ui, authinfo=None):
696 def opener(ui, authinfo=None):
681 '''
697 '''
682 construct an opener suitable for urllib2
698 construct an opener suitable for urllib2
683 authinfo will be added to the password manager
699 authinfo will be added to the password manager
684 '''
700 '''
685 handlers = [httphandler()]
701 handlers = [httphandler()]
686 if has_https:
702 if has_https:
687 handlers.append(httpshandler(ui))
703 handlers.append(httpshandler(ui))
688
704
689 handlers.append(proxyhandler(ui))
705 handlers.append(proxyhandler(ui))
690
706
691 passmgr = passwordmgr(ui)
707 passmgr = passwordmgr(ui)
692 if authinfo is not None:
708 if authinfo is not None:
693 passmgr.add_password(*authinfo)
709 passmgr.add_password(*authinfo)
694 user, passwd = authinfo[2:4]
710 user, passwd = authinfo[2:4]
695 ui.debug('http auth: user %s, password %s\n' %
711 ui.debug('http auth: user %s, password %s\n' %
696 (user, passwd and '*' * len(passwd) or 'not set'))
712 (user, passwd and '*' * len(passwd) or 'not set'))
697
713
698 handlers.extend((httpbasicauthhandler(passmgr),
714 handlers.extend((httpbasicauthhandler(passmgr),
699 httpdigestauthhandler(passmgr)))
715 httpdigestauthhandler(passmgr)))
700 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
716 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
701 opener = urllib2.build_opener(*handlers)
717 opener = urllib2.build_opener(*handlers)
702
718
703 # 1.0 here is the _protocol_ version
719 # 1.0 here is the _protocol_ version
704 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
720 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
705 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
721 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
706 return opener
722 return opener
707
723
708 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
724 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
709
725
710 def open(ui, url, data=None):
726 def open(ui, url, data=None):
711 scheme = None
727 scheme = None
712 m = scheme_re.search(url)
728 m = scheme_re.search(url)
713 if m:
729 if m:
714 scheme = m.group(1).lower()
730 scheme = m.group(1).lower()
715 if not scheme:
731 if not scheme:
716 path = util.normpath(os.path.abspath(url))
732 path = util.normpath(os.path.abspath(url))
717 url = 'file://' + urllib.pathname2url(path)
733 url = 'file://' + urllib.pathname2url(path)
718 authinfo = None
734 authinfo = None
719 else:
735 else:
720 url, authinfo = getauthinfo(url)
736 url, authinfo = getauthinfo(url)
721 return opener(ui, authinfo).open(url, data)
737 return opener(ui, authinfo).open(url, data)
@@ -1,38 +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')
51
52 # Unicode (IDN) certname isn't supported
53 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
54 'IDN in certificate not supported')
General Comments 0
You need to be logged in to leave comments. Login now