##// END OF EJS Templates
ssl: fix compatibility with pre-2.6 Python
Matt Mackall -
r10411:af4c42ec default
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 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()
271 _GLOBAL_DEFAULT_TIMEOUT = object()
272
272
273 try:
273 try:
274 _create_connection = socket.create_connection
274 _create_connection = socket.create_connection
275 except ImportError:
275 except AttributeError:
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.realhost: # use CONNECT proxy
305 if has_https and self.realhost: # 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 if ':' in urlparts[1]:
338 if ':' in urlparts[1]:
339 realhost, realport = urlparts[1].split(':')
339 realhost, realport = urlparts[1].split(':')
340 realport = int(realport)
340 realport = int(realport)
341 else:
341 else:
342 realhost = urlparts[1]
342 realhost = urlparts[1]
343 realport = 443
343 realport = 443
344
344
345 h.realhost = realhost
345 h.realhost = realhost
346 h.realport = realport
346 h.realport = realport
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.realhost = None
351 h.realhost = None
352 h.realport = None
352 h.realport = None
353 h.headers = None
353 h.headers = None
354
354
355 def _generic_proxytunnel(self):
355 def _generic_proxytunnel(self):
356 proxyheaders = dict(
356 proxyheaders = dict(
357 [(x, self.headers[x]) for x in self.headers
357 [(x, self.headers[x]) for x in self.headers
358 if x.lower().startswith('proxy-')])
358 if x.lower().startswith('proxy-')])
359 self._set_hostport(self.host, self.port)
359 self._set_hostport(self.host, self.port)
360 self.send('CONNECT %s:%d HTTP/1.0\r\n' % (self.realhost, self.realport))
360 self.send('CONNECT %s:%d HTTP/1.0\r\n' % (self.realhost, self.realport))
361 for header in proxyheaders.iteritems():
361 for header in proxyheaders.iteritems():
362 self.send('%s: %s\r\n' % header)
362 self.send('%s: %s\r\n' % header)
363 self.send('\r\n')
363 self.send('\r\n')
364
364
365 # majority of the following code is duplicated from
365 # majority of the following code is duplicated from
366 # httplib.HTTPConnection as there are no adequate places to
366 # httplib.HTTPConnection as there are no adequate places to
367 # override functions to provide the needed functionality
367 # override functions to provide the needed functionality
368 res = self.response_class(self.sock,
368 res = self.response_class(self.sock,
369 strict=self.strict,
369 strict=self.strict,
370 method=self._method)
370 method=self._method)
371
371
372 while True:
372 while True:
373 version, status, reason = res._read_status()
373 version, status, reason = res._read_status()
374 if status != httplib.CONTINUE:
374 if status != httplib.CONTINUE:
375 break
375 break
376 while True:
376 while True:
377 skip = res.fp.readline().strip()
377 skip = res.fp.readline().strip()
378 if not skip:
378 if not skip:
379 break
379 break
380 res.status = status
380 res.status = status
381 res.reason = reason.strip()
381 res.reason = reason.strip()
382
382
383 if res.status == 200:
383 if res.status == 200:
384 while True:
384 while True:
385 line = res.fp.readline()
385 line = res.fp.readline()
386 if line == '\r\n':
386 if line == '\r\n':
387 break
387 break
388 return True
388 return True
389
389
390 if version == 'HTTP/1.0':
390 if version == 'HTTP/1.0':
391 res.version = 10
391 res.version = 10
392 elif version.startswith('HTTP/1.'):
392 elif version.startswith('HTTP/1.'):
393 res.version = 11
393 res.version = 11
394 elif version == 'HTTP/0.9':
394 elif version == 'HTTP/0.9':
395 res.version = 9
395 res.version = 9
396 else:
396 else:
397 raise httplib.UnknownProtocol(version)
397 raise httplib.UnknownProtocol(version)
398
398
399 if res.version == 9:
399 if res.version == 9:
400 res.length = None
400 res.length = None
401 res.chunked = 0
401 res.chunked = 0
402 res.will_close = 1
402 res.will_close = 1
403 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
403 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
404 return False
404 return False
405
405
406 res.msg = httplib.HTTPMessage(res.fp)
406 res.msg = httplib.HTTPMessage(res.fp)
407 res.msg.fp = None
407 res.msg.fp = None
408
408
409 # are we using the chunked-style of transfer encoding?
409 # are we using the chunked-style of transfer encoding?
410 trenc = res.msg.getheader('transfer-encoding')
410 trenc = res.msg.getheader('transfer-encoding')
411 if trenc and trenc.lower() == "chunked":
411 if trenc and trenc.lower() == "chunked":
412 res.chunked = 1
412 res.chunked = 1
413 res.chunk_left = None
413 res.chunk_left = None
414 else:
414 else:
415 res.chunked = 0
415 res.chunked = 0
416
416
417 # will the connection close at the end of the response?
417 # will the connection close at the end of the response?
418 res.will_close = res._check_close()
418 res.will_close = res._check_close()
419
419
420 # do we have a Content-Length?
420 # do we have a Content-Length?
421 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
421 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
422 length = res.msg.getheader('content-length')
422 length = res.msg.getheader('content-length')
423 if length and not res.chunked:
423 if length and not res.chunked:
424 try:
424 try:
425 res.length = int(length)
425 res.length = int(length)
426 except ValueError:
426 except ValueError:
427 res.length = None
427 res.length = None
428 else:
428 else:
429 if res.length < 0: # ignore nonsensical negative lengths
429 if res.length < 0: # ignore nonsensical negative lengths
430 res.length = None
430 res.length = None
431 else:
431 else:
432 res.length = None
432 res.length = None
433
433
434 # does the body have a fixed length? (of zero)
434 # does the body have a fixed length? (of zero)
435 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
435 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
436 100 <= status < 200 or # 1xx codes
436 100 <= status < 200 or # 1xx codes
437 res._method == 'HEAD'):
437 res._method == 'HEAD'):
438 res.length = 0
438 res.length = 0
439
439
440 # if the connection remains open, and we aren't using chunked, and
440 # if the connection remains open, and we aren't using chunked, and
441 # a content-length was not provided, then assume that the connection
441 # a content-length was not provided, then assume that the connection
442 # WILL close.
442 # WILL close.
443 if (not res.will_close and
443 if (not res.will_close and
444 not res.chunked and
444 not res.chunked and
445 res.length is None):
445 res.length is None):
446 res.will_close = 1
446 res.will_close = 1
447
447
448 self.proxyres = res
448 self.proxyres = res
449
449
450 return False
450 return False
451
451
452 class httphandler(keepalive.HTTPHandler):
452 class httphandler(keepalive.HTTPHandler):
453 def http_open(self, req):
453 def http_open(self, req):
454 return self.do_open(httpconnection, req)
454 return self.do_open(httpconnection, req)
455
455
456 def _start_transaction(self, h, req):
456 def _start_transaction(self, h, req):
457 _generic_start_transaction(self, h, req)
457 _generic_start_transaction(self, h, req)
458 return keepalive.HTTPHandler._start_transaction(self, h, req)
458 return keepalive.HTTPHandler._start_transaction(self, h, req)
459
459
460 def __del__(self):
460 def __del__(self):
461 self.close_all()
461 self.close_all()
462
462
463 if has_https:
463 if has_https:
464 class BetterHTTPS(httplib.HTTPSConnection):
464 class BetterHTTPS(httplib.HTTPSConnection):
465 send = keepalive.safesend
465 send = keepalive.safesend
466
466
467 def connect(self):
467 def connect(self):
468 if hasattr(self, 'ui'):
468 if hasattr(self, 'ui'):
469 cacerts = self.ui.config('web', 'cacerts')
469 cacerts = self.ui.config('web', 'cacerts')
470 else:
470 else:
471 cacerts = None
471 cacerts = None
472
472
473 if cacerts:
473 if cacerts:
474 sock = _create_connection((self.host, self.port))
474 sock = _create_connection((self.host, self.port))
475 self.sock = _ssl_wrap_socket(sock, self.key_file,
475 self.sock = _ssl_wrap_socket(sock, self.key_file,
476 self.cert_file, cert_reqs=CERT_REQUIRED,
476 self.cert_file, cert_reqs=CERT_REQUIRED,
477 ca_certs=cacerts)
477 ca_certs=cacerts)
478 self.ui.debug(_('server identity verification succeeded\n'))
478 self.ui.debug(_('server identity verification succeeded\n'))
479 else:
479 else:
480 httplib.HTTPSConnection.connect(self)
480 httplib.HTTPSConnection.connect(self)
481
481
482 class httpsconnection(BetterHTTPS):
482 class httpsconnection(BetterHTTPS):
483 response_class = keepalive.HTTPResponse
483 response_class = keepalive.HTTPResponse
484 # must be able to send big bundle as stream.
484 # must be able to send big bundle as stream.
485 send = _gen_sendfile(BetterHTTPS)
485 send = _gen_sendfile(BetterHTTPS)
486 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
486 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
487
487
488 def connect(self):
488 def connect(self):
489 if self.realhost: # use CONNECT proxy
489 if self.realhost: # use CONNECT proxy
490 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
490 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
491 self.sock.connect((self.host, self.port))
491 self.sock.connect((self.host, self.port))
492 if _generic_proxytunnel(self):
492 if _generic_proxytunnel(self):
493 self.sock = _ssl_wrap_socket(self.sock, self.cert_file,
493 self.sock = _ssl_wrap_socket(self.sock, self.cert_file,
494 self.key_file)
494 self.key_file)
495 else:
495 else:
496 BetterHTTPS.connect(self)
496 BetterHTTPS.connect(self)
497
497
498 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
498 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
499 def __init__(self, ui):
499 def __init__(self, ui):
500 keepalive.KeepAliveHandler.__init__(self)
500 keepalive.KeepAliveHandler.__init__(self)
501 urllib2.HTTPSHandler.__init__(self)
501 urllib2.HTTPSHandler.__init__(self)
502 self.ui = ui
502 self.ui = ui
503 self.pwmgr = passwordmgr(self.ui)
503 self.pwmgr = passwordmgr(self.ui)
504
504
505 def _start_transaction(self, h, req):
505 def _start_transaction(self, h, req):
506 _generic_start_transaction(self, h, req)
506 _generic_start_transaction(self, h, req)
507 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
507 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
508
508
509 def https_open(self, req):
509 def https_open(self, req):
510 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
510 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
511 return self.do_open(self._makeconnection, req)
511 return self.do_open(self._makeconnection, req)
512
512
513 def _makeconnection(self, host, port=None, *args, **kwargs):
513 def _makeconnection(self, host, port=None, *args, **kwargs):
514 keyfile = None
514 keyfile = None
515 certfile = None
515 certfile = None
516
516
517 if args: # key_file
517 if args: # key_file
518 keyfile = args.pop(0)
518 keyfile = args.pop(0)
519 if args: # cert_file
519 if args: # cert_file
520 certfile = args.pop(0)
520 certfile = args.pop(0)
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