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