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