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