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