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