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