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