##// END OF EJS Templates
url: verify correctness of https server certificates (issue2407)...
Mads Kiilerich -
r12592:f2937d64 stable
parent child Browse files
Show More
@@ -0,0 +1,41 b''
1 #!/usr/bin/env python
2
3 def check(a, b):
4 if a != b:
5 print (a, b)
6
7 from mercurial.url import _verifycert
8
9 # Test non-wildcard certificates
10 check(_verifycert({'subject': ((('commonName', 'example.com'),),)}, 'example.com'),
11 None)
12 check(_verifycert({'subject': ((('commonName', 'example.com'),),)}, 'www.example.com'),
13 'certificate is for example.com')
14 check(_verifycert({'subject': ((('commonName', 'www.example.com'),),)}, 'example.com'),
15 'certificate is for www.example.com')
16
17 # Test wildcard certificates
18 check(_verifycert({'subject': ((('commonName', '*.example.com'),),)}, 'www.example.com'),
19 None)
20 check(_verifycert({'subject': ((('commonName', '*.example.com'),),)}, 'example.com'),
21 'certificate is for *.example.com')
22 check(_verifycert({'subject': ((('commonName', '*.example.com'),),)}, 'w.w.example.com'),
23 'certificate is for *.example.com')
24
25 # Avoid some pitfalls
26 check(_verifycert({'subject': ((('commonName', '*.foo'),),)}, 'foo'),
27 'certificate is for *.foo')
28 check(_verifycert({'subject': ((('commonName', '*o'),),)}, 'foo'),
29 'certificate is for *o')
30
31 import time
32 lastyear = time.gmtime().tm_year - 1
33 nextyear = time.gmtime().tm_year + 1
34 check(_verifycert({'notAfter': 'May 9 00:00:00 %s GMT' % lastyear}, 'example.com'),
35 'certificate expired May 9 00:00:00 %s GMT' % lastyear)
36 check(_verifycert({'notBefore': 'May 9 00:00:00 %s GMT' % nextyear}, 'example.com'),
37 'certificate not valid before May 9 00:00:00 %s GMT' % nextyear)
38 check(_verifycert({'notAfter': 'Sep 29 15:29:48 %s GMT' % nextyear, 'subject': ()}, 'example.com'),
39 'no commonName found in certificate')
40 check(_verifycert(None, 'example.com'),
41 'no certificate received')
@@ -1,657 +1,686 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, 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):
473 '''Verify that cert (in socket.getpeercert() format) matches hostname and is
474 valid at this time. CRLs and subjectAltName are not handled.
475
476 Returns error message if any problems are found and None on success.
477 '''
478 if not cert:
479 return _('no certificate received')
480 notafter = cert.get('notAfter')
481 if notafter and time.time() > ssl.cert_time_to_seconds(notafter):
482 return _('certificate expired %s') % notafter
483 notbefore = cert.get('notBefore')
484 if notbefore and time.time() < ssl.cert_time_to_seconds(notbefore):
485 return _('certificate not valid before %s') % notbefore
486 dnsname = hostname.lower()
487 for s in cert.get('subject', []):
488 key, value = s[0]
489 if key == 'commonName':
490 certname = value.lower()
491 if (certname == dnsname or
492 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
493 return None
494 return _('certificate is for %s') % certname
495 return _('no commonName found in certificate')
496
472 if has_https:
497 if has_https:
473 class BetterHTTPS(httplib.HTTPSConnection):
498 class BetterHTTPS(httplib.HTTPSConnection):
474 send = keepalive.safesend
499 send = keepalive.safesend
475
500
476 def connect(self):
501 def connect(self):
477 if hasattr(self, 'ui'):
502 if hasattr(self, 'ui'):
478 cacerts = self.ui.config('web', 'cacerts')
503 cacerts = self.ui.config('web', 'cacerts')
479 else:
504 else:
480 cacerts = None
505 cacerts = None
481
506
482 if cacerts:
507 if cacerts:
483 sock = _create_connection((self.host, self.port))
508 sock = _create_connection((self.host, self.port))
484 self.sock = _ssl_wrap_socket(sock, self.key_file,
509 self.sock = _ssl_wrap_socket(sock, self.key_file,
485 self.cert_file, cert_reqs=CERT_REQUIRED,
510 self.cert_file, cert_reqs=CERT_REQUIRED,
486 ca_certs=cacerts)
511 ca_certs=cacerts)
487 self.ui.debug(_('server identity verification succeeded\n'))
512 msg = _verifycert(self.sock.getpeercert(), self.host)
513 if msg:
514 raise util.Abort('%s certificate error: %s' % (self.host, msg))
515 self.ui.debug(_('%s certificate successfully verified\n') %
516 self.host)
488 else:
517 else:
489 httplib.HTTPSConnection.connect(self)
518 httplib.HTTPSConnection.connect(self)
490
519
491 class httpsconnection(BetterHTTPS):
520 class httpsconnection(BetterHTTPS):
492 response_class = keepalive.HTTPResponse
521 response_class = keepalive.HTTPResponse
493 # must be able to send big bundle as stream.
522 # must be able to send big bundle as stream.
494 send = _gen_sendfile(BetterHTTPS)
523 send = _gen_sendfile(BetterHTTPS)
495 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
524 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
496
525
497 def connect(self):
526 def connect(self):
498 if self.realhostport: # use CONNECT proxy
527 if self.realhostport: # use CONNECT proxy
499 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
528 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
500 self.sock.connect((self.host, self.port))
529 self.sock.connect((self.host, self.port))
501 if _generic_proxytunnel(self):
530 if _generic_proxytunnel(self):
502 self.sock = _ssl_wrap_socket(self.sock, self.cert_file,
531 self.sock = _ssl_wrap_socket(self.sock, self.cert_file,
503 self.key_file)
532 self.key_file)
504 else:
533 else:
505 BetterHTTPS.connect(self)
534 BetterHTTPS.connect(self)
506
535
507 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
536 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
508 def __init__(self, ui):
537 def __init__(self, ui):
509 keepalive.KeepAliveHandler.__init__(self)
538 keepalive.KeepAliveHandler.__init__(self)
510 urllib2.HTTPSHandler.__init__(self)
539 urllib2.HTTPSHandler.__init__(self)
511 self.ui = ui
540 self.ui = ui
512 self.pwmgr = passwordmgr(self.ui)
541 self.pwmgr = passwordmgr(self.ui)
513
542
514 def _start_transaction(self, h, req):
543 def _start_transaction(self, h, req):
515 _generic_start_transaction(self, h, req)
544 _generic_start_transaction(self, h, req)
516 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
545 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
517
546
518 def https_open(self, req):
547 def https_open(self, req):
519 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
548 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
520 return self.do_open(self._makeconnection, req)
549 return self.do_open(self._makeconnection, req)
521
550
522 def _makeconnection(self, host, port=None, *args, **kwargs):
551 def _makeconnection(self, host, port=None, *args, **kwargs):
523 keyfile = None
552 keyfile = None
524 certfile = None
553 certfile = None
525
554
526 if len(args) >= 1: # key_file
555 if len(args) >= 1: # key_file
527 keyfile = args[0]
556 keyfile = args[0]
528 if len(args) >= 2: # cert_file
557 if len(args) >= 2: # cert_file
529 certfile = args[1]
558 certfile = args[1]
530 args = args[2:]
559 args = args[2:]
531
560
532 # if the user has specified different key/cert files in
561 # if the user has specified different key/cert files in
533 # hgrc, we prefer these
562 # hgrc, we prefer these
534 if self.auth and 'key' in self.auth and 'cert' in self.auth:
563 if self.auth and 'key' in self.auth and 'cert' in self.auth:
535 keyfile = self.auth['key']
564 keyfile = self.auth['key']
536 certfile = self.auth['cert']
565 certfile = self.auth['cert']
537
566
538 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
567 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
539 conn.ui = self.ui
568 conn.ui = self.ui
540 return conn
569 return conn
541
570
542 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
571 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
543 def __init__(self, *args, **kwargs):
572 def __init__(self, *args, **kwargs):
544 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
573 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
545 self.retried_req = None
574 self.retried_req = None
546
575
547 def reset_retry_count(self):
576 def reset_retry_count(self):
548 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
577 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
549 # forever. We disable reset_retry_count completely and reset in
578 # forever. We disable reset_retry_count completely and reset in
550 # http_error_auth_reqed instead.
579 # http_error_auth_reqed instead.
551 pass
580 pass
552
581
553 def http_error_auth_reqed(self, auth_header, host, req, headers):
582 def http_error_auth_reqed(self, auth_header, host, req, headers):
554 # Reset the retry counter once for each request.
583 # Reset the retry counter once for each request.
555 if req is not self.retried_req:
584 if req is not self.retried_req:
556 self.retried_req = req
585 self.retried_req = req
557 self.retried = 0
586 self.retried = 0
558 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
587 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
559 # it doesn't know about the auth type requested. This can happen if
588 # it doesn't know about the auth type requested. This can happen if
560 # somebody is using BasicAuth and types a bad password.
589 # somebody is using BasicAuth and types a bad password.
561 try:
590 try:
562 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
591 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
563 self, auth_header, host, req, headers)
592 self, auth_header, host, req, headers)
564 except ValueError, inst:
593 except ValueError, inst:
565 arg = inst.args[0]
594 arg = inst.args[0]
566 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
595 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
567 return
596 return
568 raise
597 raise
569
598
570 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
599 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
571 def __init__(self, *args, **kwargs):
600 def __init__(self, *args, **kwargs):
572 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
601 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
573 self.retried_req = None
602 self.retried_req = None
574
603
575 def reset_retry_count(self):
604 def reset_retry_count(self):
576 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
605 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
577 # forever. We disable reset_retry_count completely and reset in
606 # forever. We disable reset_retry_count completely and reset in
578 # http_error_auth_reqed instead.
607 # http_error_auth_reqed instead.
579 pass
608 pass
580
609
581 def http_error_auth_reqed(self, auth_header, host, req, headers):
610 def http_error_auth_reqed(self, auth_header, host, req, headers):
582 # Reset the retry counter once for each request.
611 # Reset the retry counter once for each request.
583 if req is not self.retried_req:
612 if req is not self.retried_req:
584 self.retried_req = req
613 self.retried_req = req
585 self.retried = 0
614 self.retried = 0
586 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
615 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
587 self, auth_header, host, req, headers)
616 self, auth_header, host, req, headers)
588
617
589 def getauthinfo(path):
618 def getauthinfo(path):
590 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
619 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
591 if not urlpath:
620 if not urlpath:
592 urlpath = '/'
621 urlpath = '/'
593 if scheme != 'file':
622 if scheme != 'file':
594 # XXX: why are we quoting the path again with some smart
623 # XXX: why are we quoting the path again with some smart
595 # heuristic here? Anyway, it cannot be done with file://
624 # heuristic here? Anyway, it cannot be done with file://
596 # urls since path encoding is os/fs dependent (see
625 # urls since path encoding is os/fs dependent (see
597 # urllib.pathname2url() for details).
626 # urllib.pathname2url() for details).
598 urlpath = quotepath(urlpath)
627 urlpath = quotepath(urlpath)
599 host, port, user, passwd = netlocsplit(netloc)
628 host, port, user, passwd = netlocsplit(netloc)
600
629
601 # urllib cannot handle URLs with embedded user or passwd
630 # urllib cannot handle URLs with embedded user or passwd
602 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
631 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
603 urlpath, query, frag))
632 urlpath, query, frag))
604 if user:
633 if user:
605 netloc = host
634 netloc = host
606 if port:
635 if port:
607 netloc += ':' + port
636 netloc += ':' + port
608 # Python < 2.4.3 uses only the netloc to search for a password
637 # Python < 2.4.3 uses only the netloc to search for a password
609 authinfo = (None, (url, netloc), user, passwd or '')
638 authinfo = (None, (url, netloc), user, passwd or '')
610 else:
639 else:
611 authinfo = None
640 authinfo = None
612 return url, authinfo
641 return url, authinfo
613
642
614 handlerfuncs = []
643 handlerfuncs = []
615
644
616 def opener(ui, authinfo=None):
645 def opener(ui, authinfo=None):
617 '''
646 '''
618 construct an opener suitable for urllib2
647 construct an opener suitable for urllib2
619 authinfo will be added to the password manager
648 authinfo will be added to the password manager
620 '''
649 '''
621 handlers = [httphandler()]
650 handlers = [httphandler()]
622 if has_https:
651 if has_https:
623 handlers.append(httpshandler(ui))
652 handlers.append(httpshandler(ui))
624
653
625 handlers.append(proxyhandler(ui))
654 handlers.append(proxyhandler(ui))
626
655
627 passmgr = passwordmgr(ui)
656 passmgr = passwordmgr(ui)
628 if authinfo is not None:
657 if authinfo is not None:
629 passmgr.add_password(*authinfo)
658 passmgr.add_password(*authinfo)
630 user, passwd = authinfo[2:4]
659 user, passwd = authinfo[2:4]
631 ui.debug('http auth: user %s, password %s\n' %
660 ui.debug('http auth: user %s, password %s\n' %
632 (user, passwd and '*' * len(passwd) or 'not set'))
661 (user, passwd and '*' * len(passwd) or 'not set'))
633
662
634 handlers.extend((httpbasicauthhandler(passmgr),
663 handlers.extend((httpbasicauthhandler(passmgr),
635 httpdigestauthhandler(passmgr)))
664 httpdigestauthhandler(passmgr)))
636 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
665 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
637 opener = urllib2.build_opener(*handlers)
666 opener = urllib2.build_opener(*handlers)
638
667
639 # 1.0 here is the _protocol_ version
668 # 1.0 here is the _protocol_ version
640 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
669 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
641 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
670 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
642 return opener
671 return opener
643
672
644 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
673 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
645
674
646 def open(ui, url, data=None):
675 def open(ui, url, data=None):
647 scheme = None
676 scheme = None
648 m = scheme_re.search(url)
677 m = scheme_re.search(url)
649 if m:
678 if m:
650 scheme = m.group(1).lower()
679 scheme = m.group(1).lower()
651 if not scheme:
680 if not scheme:
652 path = util.normpath(os.path.abspath(url))
681 path = util.normpath(os.path.abspath(url))
653 url = 'file://' + urllib.pathname2url(path)
682 url = 'file://' + urllib.pathname2url(path)
654 authinfo = None
683 authinfo = None
655 else:
684 else:
656 url, authinfo = getauthinfo(url)
685 url, authinfo = getauthinfo(url)
657 return opener(ui, authinfo).open(url, data)
686 return opener(ui, authinfo).open(url, data)
General Comments 0
You need to be logged in to leave comments. Login now