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