##// END OF EJS Templates
Fix memory leak when using hg commands over http repositories...
Alexandre Fayolle -
r12391:ca5fd84d stable
parent child Browse files
Show More
@@ -1,660 +1,657 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 ('username', 'cert', 'key'):
159 if setting in ('username', 'cert', 'key'):
160 val = util.expandpath(val)
160 val = util.expandpath(val)
161 gdict[setting] = val
161 gdict[setting] = val
162
162
163 # Find the best match
163 # Find the best match
164 scheme, hostpath = uri.split('://', 1)
164 scheme, hostpath = uri.split('://', 1)
165 bestlen = 0
165 bestlen = 0
166 bestauth = None
166 bestauth = None
167 for auth in config.itervalues():
167 for auth in config.itervalues():
168 prefix = auth.get('prefix')
168 prefix = auth.get('prefix')
169 if not prefix:
169 if not prefix:
170 continue
170 continue
171 p = prefix.split('://', 1)
171 p = prefix.split('://', 1)
172 if len(p) > 1:
172 if len(p) > 1:
173 schemes, prefix = [p[0]], p[1]
173 schemes, prefix = [p[0]], p[1]
174 else:
174 else:
175 schemes = (auth.get('schemes') or 'https').split()
175 schemes = (auth.get('schemes') or 'https').split()
176 if (prefix == '*' or hostpath.startswith(prefix)) and \
176 if (prefix == '*' or hostpath.startswith(prefix)) and \
177 len(prefix) > bestlen and scheme in schemes:
177 len(prefix) > bestlen and scheme in schemes:
178 bestlen = len(prefix)
178 bestlen = len(prefix)
179 bestauth = auth
179 bestauth = auth
180 return bestauth
180 return bestauth
181
181
182 class proxyhandler(urllib2.ProxyHandler):
182 class proxyhandler(urllib2.ProxyHandler):
183 def __init__(self, ui):
183 def __init__(self, ui):
184 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
184 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
185 # XXX proxyauthinfo = None
185 # XXX proxyauthinfo = None
186
186
187 if proxyurl:
187 if proxyurl:
188 # proxy can be proper url or host[:port]
188 # proxy can be proper url or host[:port]
189 if not (proxyurl.startswith('http:') or
189 if not (proxyurl.startswith('http:') or
190 proxyurl.startswith('https:')):
190 proxyurl.startswith('https:')):
191 proxyurl = 'http://' + proxyurl + '/'
191 proxyurl = 'http://' + proxyurl + '/'
192 snpqf = urlparse.urlsplit(proxyurl)
192 snpqf = urlparse.urlsplit(proxyurl)
193 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
193 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
194 hpup = netlocsplit(proxynetloc)
194 hpup = netlocsplit(proxynetloc)
195
195
196 proxyhost, proxyport, proxyuser, proxypasswd = hpup
196 proxyhost, proxyport, proxyuser, proxypasswd = hpup
197 if not proxyuser:
197 if not proxyuser:
198 proxyuser = ui.config("http_proxy", "user")
198 proxyuser = ui.config("http_proxy", "user")
199 proxypasswd = ui.config("http_proxy", "passwd")
199 proxypasswd = ui.config("http_proxy", "passwd")
200
200
201 # see if we should use a proxy for this url
201 # see if we should use a proxy for this url
202 no_list = ["localhost", "127.0.0.1"]
202 no_list = ["localhost", "127.0.0.1"]
203 no_list.extend([p.lower() for
203 no_list.extend([p.lower() for
204 p in ui.configlist("http_proxy", "no")])
204 p in ui.configlist("http_proxy", "no")])
205 no_list.extend([p.strip().lower() for
205 no_list.extend([p.strip().lower() for
206 p in os.getenv("no_proxy", '').split(',')
206 p in os.getenv("no_proxy", '').split(',')
207 if p.strip()])
207 if p.strip()])
208 # "http_proxy.always" config is for running tests on localhost
208 # "http_proxy.always" config is for running tests on localhost
209 if ui.configbool("http_proxy", "always"):
209 if ui.configbool("http_proxy", "always"):
210 self.no_list = []
210 self.no_list = []
211 else:
211 else:
212 self.no_list = no_list
212 self.no_list = no_list
213
213
214 proxyurl = urlparse.urlunsplit((
214 proxyurl = urlparse.urlunsplit((
215 proxyscheme, netlocunsplit(proxyhost, proxyport,
215 proxyscheme, netlocunsplit(proxyhost, proxyport,
216 proxyuser, proxypasswd or ''),
216 proxyuser, proxypasswd or ''),
217 proxypath, proxyquery, proxyfrag))
217 proxypath, proxyquery, proxyfrag))
218 proxies = {'http': proxyurl, 'https': proxyurl}
218 proxies = {'http': proxyurl, 'https': proxyurl}
219 ui.debug('proxying through http://%s:%s\n' %
219 ui.debug('proxying through http://%s:%s\n' %
220 (proxyhost, proxyport))
220 (proxyhost, proxyport))
221 else:
221 else:
222 proxies = {}
222 proxies = {}
223
223
224 # urllib2 takes proxy values from the environment and those
224 # urllib2 takes proxy values from the environment and those
225 # will take precedence if found, so drop them
225 # will take precedence if found, so drop them
226 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
226 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
227 try:
227 try:
228 if env in os.environ:
228 if env in os.environ:
229 del os.environ[env]
229 del os.environ[env]
230 except OSError:
230 except OSError:
231 pass
231 pass
232
232
233 urllib2.ProxyHandler.__init__(self, proxies)
233 urllib2.ProxyHandler.__init__(self, proxies)
234 self.ui = ui
234 self.ui = ui
235
235
236 def proxy_open(self, req, proxy, type_):
236 def proxy_open(self, req, proxy, type_):
237 host = req.get_host().split(':')[0]
237 host = req.get_host().split(':')[0]
238 if host in self.no_list:
238 if host in self.no_list:
239 return None
239 return None
240
240
241 # work around a bug in Python < 2.4.2
241 # work around a bug in Python < 2.4.2
242 # (it leaves a "\n" at the end of Proxy-authorization headers)
242 # (it leaves a "\n" at the end of Proxy-authorization headers)
243 baseclass = req.__class__
243 baseclass = req.__class__
244 class _request(baseclass):
244 class _request(baseclass):
245 def add_header(self, key, val):
245 def add_header(self, key, val):
246 if key.lower() == 'proxy-authorization':
246 if key.lower() == 'proxy-authorization':
247 val = val.strip()
247 val = val.strip()
248 return baseclass.add_header(self, key, val)
248 return baseclass.add_header(self, key, val)
249 req.__class__ = _request
249 req.__class__ = _request
250
250
251 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
251 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
252
252
253 class httpsendfile(file):
253 class httpsendfile(file):
254 def __len__(self):
254 def __len__(self):
255 return os.fstat(self.fileno()).st_size
255 return os.fstat(self.fileno()).st_size
256
256
257 def _gen_sendfile(connection):
257 def _gen_sendfile(connection):
258 def _sendfile(self, data):
258 def _sendfile(self, data):
259 # send a file
259 # send a file
260 if isinstance(data, httpsendfile):
260 if isinstance(data, httpsendfile):
261 # if auth required, some data sent twice, so rewind here
261 # if auth required, some data sent twice, so rewind here
262 data.seek(0)
262 data.seek(0)
263 for chunk in util.filechunkiter(data):
263 for chunk in util.filechunkiter(data):
264 connection.send(self, chunk)
264 connection.send(self, chunk)
265 else:
265 else:
266 connection.send(self, data)
266 connection.send(self, data)
267 return _sendfile
267 return _sendfile
268
268
269 has_https = hasattr(urllib2, 'HTTPSHandler')
269 has_https = hasattr(urllib2, 'HTTPSHandler')
270 if has_https:
270 if has_https:
271 try:
271 try:
272 # avoid using deprecated/broken FakeSocket in python 2.6
272 # avoid using deprecated/broken FakeSocket in python 2.6
273 import ssl
273 import ssl
274 _ssl_wrap_socket = ssl.wrap_socket
274 _ssl_wrap_socket = ssl.wrap_socket
275 CERT_REQUIRED = ssl.CERT_REQUIRED
275 CERT_REQUIRED = ssl.CERT_REQUIRED
276 except ImportError:
276 except ImportError:
277 CERT_REQUIRED = 2
277 CERT_REQUIRED = 2
278
278
279 def _ssl_wrap_socket(sock, key_file, cert_file,
279 def _ssl_wrap_socket(sock, key_file, cert_file,
280 cert_reqs=CERT_REQUIRED, ca_certs=None):
280 cert_reqs=CERT_REQUIRED, ca_certs=None):
281 if ca_certs:
281 if ca_certs:
282 raise util.Abort(_(
282 raise util.Abort(_(
283 'certificate checking requires Python 2.6'))
283 'certificate checking requires Python 2.6'))
284
284
285 ssl = socket.ssl(sock, key_file, cert_file)
285 ssl = socket.ssl(sock, key_file, cert_file)
286 return httplib.FakeSocket(sock, ssl)
286 return httplib.FakeSocket(sock, ssl)
287
287
288 try:
288 try:
289 _create_connection = socket.create_connection
289 _create_connection = socket.create_connection
290 except AttributeError:
290 except AttributeError:
291 _GLOBAL_DEFAULT_TIMEOUT = object()
291 _GLOBAL_DEFAULT_TIMEOUT = object()
292
292
293 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
293 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
294 source_address=None):
294 source_address=None):
295 # lifted from Python 2.6
295 # lifted from Python 2.6
296
296
297 msg = "getaddrinfo returns an empty list"
297 msg = "getaddrinfo returns an empty list"
298 host, port = address
298 host, port = address
299 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
299 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
300 af, socktype, proto, canonname, sa = res
300 af, socktype, proto, canonname, sa = res
301 sock = None
301 sock = None
302 try:
302 try:
303 sock = socket.socket(af, socktype, proto)
303 sock = socket.socket(af, socktype, proto)
304 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
304 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
305 sock.settimeout(timeout)
305 sock.settimeout(timeout)
306 if source_address:
306 if source_address:
307 sock.bind(source_address)
307 sock.bind(source_address)
308 sock.connect(sa)
308 sock.connect(sa)
309 return sock
309 return sock
310
310
311 except socket.error, msg:
311 except socket.error, msg:
312 if sock is not None:
312 if sock is not None:
313 sock.close()
313 sock.close()
314
314
315 raise socket.error, msg
315 raise socket.error, msg
316
316
317 class httpconnection(keepalive.HTTPConnection):
317 class httpconnection(keepalive.HTTPConnection):
318 # must be able to send big bundle as stream.
318 # must be able to send big bundle as stream.
319 send = _gen_sendfile(keepalive.HTTPConnection)
319 send = _gen_sendfile(keepalive.HTTPConnection)
320
320
321 def connect(self):
321 def connect(self):
322 if has_https and self.realhostport: # use CONNECT proxy
322 if has_https and self.realhostport: # use CONNECT proxy
323 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
323 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
324 self.sock.connect((self.host, self.port))
324 self.sock.connect((self.host, self.port))
325 if _generic_proxytunnel(self):
325 if _generic_proxytunnel(self):
326 # we do not support client x509 certificates
326 # we do not support client x509 certificates
327 self.sock = _ssl_wrap_socket(self.sock, None, None)
327 self.sock = _ssl_wrap_socket(self.sock, None, None)
328 else:
328 else:
329 keepalive.HTTPConnection.connect(self)
329 keepalive.HTTPConnection.connect(self)
330
330
331 def getresponse(self):
331 def getresponse(self):
332 proxyres = getattr(self, 'proxyres', None)
332 proxyres = getattr(self, 'proxyres', None)
333 if proxyres:
333 if proxyres:
334 if proxyres.will_close:
334 if proxyres.will_close:
335 self.close()
335 self.close()
336 self.proxyres = None
336 self.proxyres = None
337 return proxyres
337 return proxyres
338 return keepalive.HTTPConnection.getresponse(self)
338 return keepalive.HTTPConnection.getresponse(self)
339
339
340 # general transaction handler to support different ways to handle
340 # general transaction handler to support different ways to handle
341 # HTTPS proxying before and after Python 2.6.3.
341 # HTTPS proxying before and after Python 2.6.3.
342 def _generic_start_transaction(handler, h, req):
342 def _generic_start_transaction(handler, h, req):
343 if hasattr(req, '_tunnel_host') and req._tunnel_host:
343 if hasattr(req, '_tunnel_host') and req._tunnel_host:
344 tunnel_host = req._tunnel_host
344 tunnel_host = req._tunnel_host
345 if tunnel_host[:7] not in ['http://', 'https:/']:
345 if tunnel_host[:7] not in ['http://', 'https:/']:
346 tunnel_host = 'https://' + tunnel_host
346 tunnel_host = 'https://' + tunnel_host
347 new_tunnel = True
347 new_tunnel = True
348 else:
348 else:
349 tunnel_host = req.get_selector()
349 tunnel_host = req.get_selector()
350 new_tunnel = False
350 new_tunnel = False
351
351
352 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
352 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
353 urlparts = urlparse.urlparse(tunnel_host)
353 urlparts = urlparse.urlparse(tunnel_host)
354 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
354 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
355 realhostport = urlparts[1]
355 realhostport = urlparts[1]
356 if realhostport[-1] == ']' or ':' not in realhostport:
356 if realhostport[-1] == ']' or ':' not in realhostport:
357 realhostport += ':443'
357 realhostport += ':443'
358
358
359 h.realhostport = realhostport
359 h.realhostport = realhostport
360 h.headers = req.headers.copy()
360 h.headers = req.headers.copy()
361 h.headers.update(handler.parent.addheaders)
361 h.headers.update(handler.parent.addheaders)
362 return
362 return
363
363
364 h.realhostport = None
364 h.realhostport = None
365 h.headers = None
365 h.headers = None
366
366
367 def _generic_proxytunnel(self):
367 def _generic_proxytunnel(self):
368 proxyheaders = dict(
368 proxyheaders = dict(
369 [(x, self.headers[x]) for x in self.headers
369 [(x, self.headers[x]) for x in self.headers
370 if x.lower().startswith('proxy-')])
370 if x.lower().startswith('proxy-')])
371 self._set_hostport(self.host, self.port)
371 self._set_hostport(self.host, self.port)
372 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
372 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
373 for header in proxyheaders.iteritems():
373 for header in proxyheaders.iteritems():
374 self.send('%s: %s\r\n' % header)
374 self.send('%s: %s\r\n' % header)
375 self.send('\r\n')
375 self.send('\r\n')
376
376
377 # majority of the following code is duplicated from
377 # majority of the following code is duplicated from
378 # httplib.HTTPConnection as there are no adequate places to
378 # httplib.HTTPConnection as there are no adequate places to
379 # override functions to provide the needed functionality
379 # override functions to provide the needed functionality
380 res = self.response_class(self.sock,
380 res = self.response_class(self.sock,
381 strict=self.strict,
381 strict=self.strict,
382 method=self._method)
382 method=self._method)
383
383
384 while True:
384 while True:
385 version, status, reason = res._read_status()
385 version, status, reason = res._read_status()
386 if status != httplib.CONTINUE:
386 if status != httplib.CONTINUE:
387 break
387 break
388 while True:
388 while True:
389 skip = res.fp.readline().strip()
389 skip = res.fp.readline().strip()
390 if not skip:
390 if not skip:
391 break
391 break
392 res.status = status
392 res.status = status
393 res.reason = reason.strip()
393 res.reason = reason.strip()
394
394
395 if res.status == 200:
395 if res.status == 200:
396 while True:
396 while True:
397 line = res.fp.readline()
397 line = res.fp.readline()
398 if line == '\r\n':
398 if line == '\r\n':
399 break
399 break
400 return True
400 return True
401
401
402 if version == 'HTTP/1.0':
402 if version == 'HTTP/1.0':
403 res.version = 10
403 res.version = 10
404 elif version.startswith('HTTP/1.'):
404 elif version.startswith('HTTP/1.'):
405 res.version = 11
405 res.version = 11
406 elif version == 'HTTP/0.9':
406 elif version == 'HTTP/0.9':
407 res.version = 9
407 res.version = 9
408 else:
408 else:
409 raise httplib.UnknownProtocol(version)
409 raise httplib.UnknownProtocol(version)
410
410
411 if res.version == 9:
411 if res.version == 9:
412 res.length = None
412 res.length = None
413 res.chunked = 0
413 res.chunked = 0
414 res.will_close = 1
414 res.will_close = 1
415 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
415 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
416 return False
416 return False
417
417
418 res.msg = httplib.HTTPMessage(res.fp)
418 res.msg = httplib.HTTPMessage(res.fp)
419 res.msg.fp = None
419 res.msg.fp = None
420
420
421 # are we using the chunked-style of transfer encoding?
421 # are we using the chunked-style of transfer encoding?
422 trenc = res.msg.getheader('transfer-encoding')
422 trenc = res.msg.getheader('transfer-encoding')
423 if trenc and trenc.lower() == "chunked":
423 if trenc and trenc.lower() == "chunked":
424 res.chunked = 1
424 res.chunked = 1
425 res.chunk_left = None
425 res.chunk_left = None
426 else:
426 else:
427 res.chunked = 0
427 res.chunked = 0
428
428
429 # will the connection close at the end of the response?
429 # will the connection close at the end of the response?
430 res.will_close = res._check_close()
430 res.will_close = res._check_close()
431
431
432 # do we have a Content-Length?
432 # do we have a Content-Length?
433 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
433 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
434 length = res.msg.getheader('content-length')
434 length = res.msg.getheader('content-length')
435 if length and not res.chunked:
435 if length and not res.chunked:
436 try:
436 try:
437 res.length = int(length)
437 res.length = int(length)
438 except ValueError:
438 except ValueError:
439 res.length = None
439 res.length = None
440 else:
440 else:
441 if res.length < 0: # ignore nonsensical negative lengths
441 if res.length < 0: # ignore nonsensical negative lengths
442 res.length = None
442 res.length = None
443 else:
443 else:
444 res.length = None
444 res.length = None
445
445
446 # does the body have a fixed length? (of zero)
446 # does the body have a fixed length? (of zero)
447 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
447 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
448 100 <= status < 200 or # 1xx codes
448 100 <= status < 200 or # 1xx codes
449 res._method == 'HEAD'):
449 res._method == 'HEAD'):
450 res.length = 0
450 res.length = 0
451
451
452 # if the connection remains open, and we aren't using chunked, and
452 # if the connection remains open, and we aren't using chunked, and
453 # a content-length was not provided, then assume that the connection
453 # a content-length was not provided, then assume that the connection
454 # WILL close.
454 # WILL close.
455 if (not res.will_close and
455 if (not res.will_close and
456 not res.chunked and
456 not res.chunked and
457 res.length is None):
457 res.length is None):
458 res.will_close = 1
458 res.will_close = 1
459
459
460 self.proxyres = res
460 self.proxyres = res
461
461
462 return False
462 return False
463
463
464 class httphandler(keepalive.HTTPHandler):
464 class httphandler(keepalive.HTTPHandler):
465 def http_open(self, req):
465 def http_open(self, req):
466 return self.do_open(httpconnection, req)
466 return self.do_open(httpconnection, req)
467
467
468 def _start_transaction(self, h, req):
468 def _start_transaction(self, h, req):
469 _generic_start_transaction(self, h, req)
469 _generic_start_transaction(self, h, req)
470 return keepalive.HTTPHandler._start_transaction(self, h, req)
470 return keepalive.HTTPHandler._start_transaction(self, h, req)
471
471
472 def __del__(self):
473 self.close_all()
474
475 if has_https:
472 if has_https:
476 class BetterHTTPS(httplib.HTTPSConnection):
473 class BetterHTTPS(httplib.HTTPSConnection):
477 send = keepalive.safesend
474 send = keepalive.safesend
478
475
479 def connect(self):
476 def connect(self):
480 if hasattr(self, 'ui'):
477 if hasattr(self, 'ui'):
481 cacerts = self.ui.config('web', 'cacerts')
478 cacerts = self.ui.config('web', 'cacerts')
482 else:
479 else:
483 cacerts = None
480 cacerts = None
484
481
485 if cacerts:
482 if cacerts:
486 sock = _create_connection((self.host, self.port))
483 sock = _create_connection((self.host, self.port))
487 self.sock = _ssl_wrap_socket(sock, self.key_file,
484 self.sock = _ssl_wrap_socket(sock, self.key_file,
488 self.cert_file, cert_reqs=CERT_REQUIRED,
485 self.cert_file, cert_reqs=CERT_REQUIRED,
489 ca_certs=cacerts)
486 ca_certs=cacerts)
490 self.ui.debug(_('server identity verification succeeded\n'))
487 self.ui.debug(_('server identity verification succeeded\n'))
491 else:
488 else:
492 httplib.HTTPSConnection.connect(self)
489 httplib.HTTPSConnection.connect(self)
493
490
494 class httpsconnection(BetterHTTPS):
491 class httpsconnection(BetterHTTPS):
495 response_class = keepalive.HTTPResponse
492 response_class = keepalive.HTTPResponse
496 # must be able to send big bundle as stream.
493 # must be able to send big bundle as stream.
497 send = _gen_sendfile(BetterHTTPS)
494 send = _gen_sendfile(BetterHTTPS)
498 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
495 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
499
496
500 def connect(self):
497 def connect(self):
501 if self.realhostport: # use CONNECT proxy
498 if self.realhostport: # use CONNECT proxy
502 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
499 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
503 self.sock.connect((self.host, self.port))
500 self.sock.connect((self.host, self.port))
504 if _generic_proxytunnel(self):
501 if _generic_proxytunnel(self):
505 self.sock = _ssl_wrap_socket(self.sock, self.cert_file,
502 self.sock = _ssl_wrap_socket(self.sock, self.cert_file,
506 self.key_file)
503 self.key_file)
507 else:
504 else:
508 BetterHTTPS.connect(self)
505 BetterHTTPS.connect(self)
509
506
510 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
507 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
511 def __init__(self, ui):
508 def __init__(self, ui):
512 keepalive.KeepAliveHandler.__init__(self)
509 keepalive.KeepAliveHandler.__init__(self)
513 urllib2.HTTPSHandler.__init__(self)
510 urllib2.HTTPSHandler.__init__(self)
514 self.ui = ui
511 self.ui = ui
515 self.pwmgr = passwordmgr(self.ui)
512 self.pwmgr = passwordmgr(self.ui)
516
513
517 def _start_transaction(self, h, req):
514 def _start_transaction(self, h, req):
518 _generic_start_transaction(self, h, req)
515 _generic_start_transaction(self, h, req)
519 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
516 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
520
517
521 def https_open(self, req):
518 def https_open(self, req):
522 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
519 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
523 return self.do_open(self._makeconnection, req)
520 return self.do_open(self._makeconnection, req)
524
521
525 def _makeconnection(self, host, port=None, *args, **kwargs):
522 def _makeconnection(self, host, port=None, *args, **kwargs):
526 keyfile = None
523 keyfile = None
527 certfile = None
524 certfile = None
528
525
529 if len(args) >= 1: # key_file
526 if len(args) >= 1: # key_file
530 keyfile = args[0]
527 keyfile = args[0]
531 if len(args) >= 2: # cert_file
528 if len(args) >= 2: # cert_file
532 certfile = args[1]
529 certfile = args[1]
533 args = args[2:]
530 args = args[2:]
534
531
535 # if the user has specified different key/cert files in
532 # if the user has specified different key/cert files in
536 # hgrc, we prefer these
533 # hgrc, we prefer these
537 if self.auth and 'key' in self.auth and 'cert' in self.auth:
534 if self.auth and 'key' in self.auth and 'cert' in self.auth:
538 keyfile = self.auth['key']
535 keyfile = self.auth['key']
539 certfile = self.auth['cert']
536 certfile = self.auth['cert']
540
537
541 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
538 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
542 conn.ui = self.ui
539 conn.ui = self.ui
543 return conn
540 return conn
544
541
545 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
542 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
546 def __init__(self, *args, **kwargs):
543 def __init__(self, *args, **kwargs):
547 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
544 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
548 self.retried_req = None
545 self.retried_req = None
549
546
550 def reset_retry_count(self):
547 def reset_retry_count(self):
551 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
548 # 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
549 # forever. We disable reset_retry_count completely and reset in
553 # http_error_auth_reqed instead.
550 # http_error_auth_reqed instead.
554 pass
551 pass
555
552
556 def http_error_auth_reqed(self, auth_header, host, req, headers):
553 def http_error_auth_reqed(self, auth_header, host, req, headers):
557 # Reset the retry counter once for each request.
554 # Reset the retry counter once for each request.
558 if req is not self.retried_req:
555 if req is not self.retried_req:
559 self.retried_req = req
556 self.retried_req = req
560 self.retried = 0
557 self.retried = 0
561 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
558 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
562 # it doesn't know about the auth type requested. This can happen if
559 # it doesn't know about the auth type requested. This can happen if
563 # somebody is using BasicAuth and types a bad password.
560 # somebody is using BasicAuth and types a bad password.
564 try:
561 try:
565 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
562 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
566 self, auth_header, host, req, headers)
563 self, auth_header, host, req, headers)
567 except ValueError, inst:
564 except ValueError, inst:
568 arg = inst.args[0]
565 arg = inst.args[0]
569 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
566 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
570 return
567 return
571 raise
568 raise
572
569
573 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
570 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
574 def __init__(self, *args, **kwargs):
571 def __init__(self, *args, **kwargs):
575 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
572 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
576 self.retried_req = None
573 self.retried_req = None
577
574
578 def reset_retry_count(self):
575 def reset_retry_count(self):
579 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
576 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
580 # forever. We disable reset_retry_count completely and reset in
577 # forever. We disable reset_retry_count completely and reset in
581 # http_error_auth_reqed instead.
578 # http_error_auth_reqed instead.
582 pass
579 pass
583
580
584 def http_error_auth_reqed(self, auth_header, host, req, headers):
581 def http_error_auth_reqed(self, auth_header, host, req, headers):
585 # Reset the retry counter once for each request.
582 # Reset the retry counter once for each request.
586 if req is not self.retried_req:
583 if req is not self.retried_req:
587 self.retried_req = req
584 self.retried_req = req
588 self.retried = 0
585 self.retried = 0
589 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
586 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
590 self, auth_header, host, req, headers)
587 self, auth_header, host, req, headers)
591
588
592 def getauthinfo(path):
589 def getauthinfo(path):
593 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
590 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
594 if not urlpath:
591 if not urlpath:
595 urlpath = '/'
592 urlpath = '/'
596 if scheme != 'file':
593 if scheme != 'file':
597 # XXX: why are we quoting the path again with some smart
594 # XXX: why are we quoting the path again with some smart
598 # heuristic here? Anyway, it cannot be done with file://
595 # heuristic here? Anyway, it cannot be done with file://
599 # urls since path encoding is os/fs dependent (see
596 # urls since path encoding is os/fs dependent (see
600 # urllib.pathname2url() for details).
597 # urllib.pathname2url() for details).
601 urlpath = quotepath(urlpath)
598 urlpath = quotepath(urlpath)
602 host, port, user, passwd = netlocsplit(netloc)
599 host, port, user, passwd = netlocsplit(netloc)
603
600
604 # urllib cannot handle URLs with embedded user or passwd
601 # urllib cannot handle URLs with embedded user or passwd
605 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
602 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
606 urlpath, query, frag))
603 urlpath, query, frag))
607 if user:
604 if user:
608 netloc = host
605 netloc = host
609 if port:
606 if port:
610 netloc += ':' + port
607 netloc += ':' + port
611 # Python < 2.4.3 uses only the netloc to search for a password
608 # Python < 2.4.3 uses only the netloc to search for a password
612 authinfo = (None, (url, netloc), user, passwd or '')
609 authinfo = (None, (url, netloc), user, passwd or '')
613 else:
610 else:
614 authinfo = None
611 authinfo = None
615 return url, authinfo
612 return url, authinfo
616
613
617 handlerfuncs = []
614 handlerfuncs = []
618
615
619 def opener(ui, authinfo=None):
616 def opener(ui, authinfo=None):
620 '''
617 '''
621 construct an opener suitable for urllib2
618 construct an opener suitable for urllib2
622 authinfo will be added to the password manager
619 authinfo will be added to the password manager
623 '''
620 '''
624 handlers = [httphandler()]
621 handlers = [httphandler()]
625 if has_https:
622 if has_https:
626 handlers.append(httpshandler(ui))
623 handlers.append(httpshandler(ui))
627
624
628 handlers.append(proxyhandler(ui))
625 handlers.append(proxyhandler(ui))
629
626
630 passmgr = passwordmgr(ui)
627 passmgr = passwordmgr(ui)
631 if authinfo is not None:
628 if authinfo is not None:
632 passmgr.add_password(*authinfo)
629 passmgr.add_password(*authinfo)
633 user, passwd = authinfo[2:4]
630 user, passwd = authinfo[2:4]
634 ui.debug('http auth: user %s, password %s\n' %
631 ui.debug('http auth: user %s, password %s\n' %
635 (user, passwd and '*' * len(passwd) or 'not set'))
632 (user, passwd and '*' * len(passwd) or 'not set'))
636
633
637 handlers.extend((httpbasicauthhandler(passmgr),
634 handlers.extend((httpbasicauthhandler(passmgr),
638 httpdigestauthhandler(passmgr)))
635 httpdigestauthhandler(passmgr)))
639 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
636 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
640 opener = urllib2.build_opener(*handlers)
637 opener = urllib2.build_opener(*handlers)
641
638
642 # 1.0 here is the _protocol_ version
639 # 1.0 here is the _protocol_ version
643 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
640 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
644 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
641 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
645 return opener
642 return opener
646
643
647 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
644 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
648
645
649 def open(ui, url, data=None):
646 def open(ui, url, data=None):
650 scheme = None
647 scheme = None
651 m = scheme_re.search(url)
648 m = scheme_re.search(url)
652 if m:
649 if m:
653 scheme = m.group(1).lower()
650 scheme = m.group(1).lower()
654 if not scheme:
651 if not scheme:
655 path = util.normpath(os.path.abspath(url))
652 path = util.normpath(os.path.abspath(url))
656 url = 'file://' + urllib.pathname2url(path)
653 url = 'file://' + urllib.pathname2url(path)
657 authinfo = None
654 authinfo = None
658 else:
655 else:
659 url, authinfo = getauthinfo(url)
656 url, authinfo = getauthinfo(url)
660 return opener(ui, authinfo).open(url, data)
657 return opener(ui, authinfo).open(url, data)
General Comments 0
You need to be logged in to leave comments. Login now