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