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