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