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