##// END OF EJS Templates
url: abort on file:// URLs with non-localhost hosts
Brodie Rao -
r13817:7f18bab2 default
parent child Browse files
Show More
@@ -1,972 +1,977 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 class url(object):
15 class url(object):
16 """Reliable URL parser.
16 """Reliable URL parser.
17
17
18 This parses URLs and provides attributes for the following
18 This parses URLs and provides attributes for the following
19 components:
19 components:
20
20
21 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
21 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
22
22
23 Missing components are set to None. The only exception is
23 Missing components are set to None. The only exception is
24 fragment, which is set to '' if present but empty.
24 fragment, which is set to '' if present but empty.
25
25
26 If parse_fragment is False, fragment is included in query. If
26 If parse_fragment is False, fragment is included in query. If
27 parse_query is False, query is included in path. If both are
27 parse_query is False, query is included in path. If both are
28 False, both fragment and query are included in path.
28 False, both fragment and query are included in path.
29
29
30 See http://www.ietf.org/rfc/rfc2396.txt for more information.
30 See http://www.ietf.org/rfc/rfc2396.txt for more information.
31
31
32 Note that for backward compatibility reasons, bundle URLs do not
32 Note that for backward compatibility reasons, bundle URLs do not
33 take host names. That means 'bundle://../' has a path of '../'.
33 take host names. That means 'bundle://../' has a path of '../'.
34
34
35 Examples:
35 Examples:
36
36
37 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
37 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
38 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
38 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
39 >>> url('ssh://[::1]:2200//home/joe/repo')
39 >>> url('ssh://[::1]:2200//home/joe/repo')
40 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
40 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
41 >>> url('file:///home/joe/repo')
41 >>> url('file:///home/joe/repo')
42 <url scheme: 'file', path: '/home/joe/repo'>
42 <url scheme: 'file', path: '/home/joe/repo'>
43 >>> url('bundle:foo')
43 >>> url('bundle:foo')
44 <url scheme: 'bundle', path: 'foo'>
44 <url scheme: 'bundle', path: 'foo'>
45 >>> url('bundle://../foo')
45 >>> url('bundle://../foo')
46 <url scheme: 'bundle', path: '../foo'>
46 <url scheme: 'bundle', path: '../foo'>
47 >>> url('c:\\\\foo\\\\bar')
47 >>> url('c:\\\\foo\\\\bar')
48 <url path: 'c:\\\\foo\\\\bar'>
48 <url path: 'c:\\\\foo\\\\bar'>
49
49
50 Authentication credentials:
50 Authentication credentials:
51
51
52 >>> url('ssh://joe:xyz@x/repo')
52 >>> url('ssh://joe:xyz@x/repo')
53 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
53 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
54 >>> url('ssh://joe@x/repo')
54 >>> url('ssh://joe@x/repo')
55 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
55 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
56
56
57 Query strings and fragments:
57 Query strings and fragments:
58
58
59 >>> url('http://host/a?b#c')
59 >>> url('http://host/a?b#c')
60 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
60 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
61 >>> url('http://host/a?b#c', parse_query=False, parse_fragment=False)
61 >>> url('http://host/a?b#c', parse_query=False, parse_fragment=False)
62 <url scheme: 'http', host: 'host', path: 'a?b#c'>
62 <url scheme: 'http', host: 'host', path: 'a?b#c'>
63 """
63 """
64
64
65 _safechars = "!~*'()+"
65 _safechars = "!~*'()+"
66 _safepchars = "/!~*'()+"
66 _safepchars = "/!~*'()+"
67
67
68 def __init__(self, path, parse_query=True, parse_fragment=True):
68 def __init__(self, path, parse_query=True, parse_fragment=True):
69 # We slowly chomp away at path until we have only the path left
69 # We slowly chomp away at path until we have only the path left
70 self.scheme = self.user = self.passwd = self.host = None
70 self.scheme = self.user = self.passwd = self.host = None
71 self.port = self.path = self.query = self.fragment = None
71 self.port = self.path = self.query = self.fragment = None
72 self._localpath = True
72 self._localpath = True
73
73
74 # special case for Windows drive letters
74 # special case for Windows drive letters
75 if has_drive_letter(path):
75 if has_drive_letter(path):
76 self.path = path
76 self.path = path
77 return
77 return
78
78
79 # For compatibility reasons, we can't handle bundle paths as
79 # For compatibility reasons, we can't handle bundle paths as
80 # normal URLS
80 # normal URLS
81 if path.startswith('bundle:'):
81 if path.startswith('bundle:'):
82 self.scheme = 'bundle'
82 self.scheme = 'bundle'
83 path = path[7:]
83 path = path[7:]
84 if path.startswith('//'):
84 if path.startswith('//'):
85 path = path[2:]
85 path = path[2:]
86 self.path = path
86 self.path = path
87 return
87 return
88
88
89 if not path.startswith('/') and ':' in path:
89 if not path.startswith('/') and ':' in path:
90 parts = path.split(':', 1)
90 parts = path.split(':', 1)
91 if parts[0]:
91 if parts[0]:
92 self.scheme, path = parts
92 self.scheme, path = parts
93 self._localpath = False
93 self._localpath = False
94
94
95 if not path:
95 if not path:
96 path = None
96 path = None
97 if self._localpath:
97 if self._localpath:
98 self.path = ''
98 self.path = ''
99 return
99 return
100 else:
100 else:
101 if parse_fragment and '#' in path:
101 if parse_fragment and '#' in path:
102 path, self.fragment = path.split('#', 1)
102 path, self.fragment = path.split('#', 1)
103 if not path:
103 if not path:
104 path = None
104 path = None
105 if self._localpath:
105 if self._localpath:
106 self.path = path
106 self.path = path
107 return
107 return
108
108
109 if parse_query and '?' in path:
109 if parse_query and '?' in path:
110 path, self.query = path.split('?', 1)
110 path, self.query = path.split('?', 1)
111 if not path:
111 if not path:
112 path = None
112 path = None
113 if not self.query:
113 if not self.query:
114 self.query = None
114 self.query = None
115
115
116 # // is required to specify a host/authority
116 # // is required to specify a host/authority
117 if path and path.startswith('//'):
117 if path and path.startswith('//'):
118 parts = path[2:].split('/', 1)
118 parts = path[2:].split('/', 1)
119 if len(parts) > 1:
119 if len(parts) > 1:
120 self.host, path = parts
120 self.host, path = parts
121 path = path
121 path = path
122 else:
122 else:
123 self.host = parts[0]
123 self.host = parts[0]
124 path = None
124 path = None
125 if not self.host:
125 if not self.host:
126 self.host = None
126 self.host = None
127 if path:
127 if path:
128 path = '/' + path
128 path = '/' + path
129
129
130 if self.host and '@' in self.host:
130 if self.host and '@' in self.host:
131 self.user, self.host = self.host.rsplit('@', 1)
131 self.user, self.host = self.host.rsplit('@', 1)
132 if ':' in self.user:
132 if ':' in self.user:
133 self.user, self.passwd = self.user.split(':', 1)
133 self.user, self.passwd = self.user.split(':', 1)
134 if not self.host:
134 if not self.host:
135 self.host = None
135 self.host = None
136
136
137 # Don't split on colons in IPv6 addresses without ports
137 # Don't split on colons in IPv6 addresses without ports
138 if (self.host and ':' in self.host and
138 if (self.host and ':' in self.host and
139 not (self.host.startswith('[') and self.host.endswith(']'))):
139 not (self.host.startswith('[') and self.host.endswith(']'))):
140 self.host, self.port = self.host.rsplit(':', 1)
140 self.host, self.port = self.host.rsplit(':', 1)
141 if not self.host:
141 if not self.host:
142 self.host = None
142 self.host = None
143
144 if (self.host and self.scheme == 'file' and
145 self.host not in ('localhost', '127.0.0.1', '[::1]')):
146 raise util.Abort(_('file:// URLs can only refer to localhost'))
147
143 self.path = path
148 self.path = path
144
149
145 for a in ('user', 'passwd', 'host', 'port',
150 for a in ('user', 'passwd', 'host', 'port',
146 'path', 'query', 'fragment'):
151 'path', 'query', 'fragment'):
147 v = getattr(self, a)
152 v = getattr(self, a)
148 if v is not None:
153 if v is not None:
149 setattr(self, a, urllib.unquote(v))
154 setattr(self, a, urllib.unquote(v))
150
155
151 def __repr__(self):
156 def __repr__(self):
152 attrs = []
157 attrs = []
153 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
158 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
154 'query', 'fragment'):
159 'query', 'fragment'):
155 v = getattr(self, a)
160 v = getattr(self, a)
156 if v is not None:
161 if v is not None:
157 attrs.append('%s: %r' % (a, v))
162 attrs.append('%s: %r' % (a, v))
158 return '<url %s>' % ', '.join(attrs)
163 return '<url %s>' % ', '.join(attrs)
159
164
160 def __str__(self):
165 def __str__(self):
161 """Join the URL's components back into a URL string.
166 """Join the URL's components back into a URL string.
162
167
163 Examples:
168 Examples:
164
169
165 >>> str(url('http://user:pw@host:80/?foo#bar'))
170 >>> str(url('http://user:pw@host:80/?foo#bar'))
166 'http://user:pw@host:80/?foo#bar'
171 'http://user:pw@host:80/?foo#bar'
167 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
172 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
168 'ssh://user:pw@[::1]:2200//home/joe#'
173 'ssh://user:pw@[::1]:2200//home/joe#'
169 >>> str(url('http://localhost:80//'))
174 >>> str(url('http://localhost:80//'))
170 'http://localhost:80//'
175 'http://localhost:80//'
171 >>> str(url('http://localhost:80/'))
176 >>> str(url('http://localhost:80/'))
172 'http://localhost:80/'
177 'http://localhost:80/'
173 >>> str(url('http://localhost:80'))
178 >>> str(url('http://localhost:80'))
174 'http://localhost:80/'
179 'http://localhost:80/'
175 >>> str(url('bundle:foo'))
180 >>> str(url('bundle:foo'))
176 'bundle:foo'
181 'bundle:foo'
177 >>> str(url('bundle://../foo'))
182 >>> str(url('bundle://../foo'))
178 'bundle:../foo'
183 'bundle:../foo'
179 >>> str(url('path'))
184 >>> str(url('path'))
180 'path'
185 'path'
181 """
186 """
182 if self._localpath:
187 if self._localpath:
183 s = self.path
188 s = self.path
184 if self.scheme == 'bundle':
189 if self.scheme == 'bundle':
185 s = 'bundle:' + s
190 s = 'bundle:' + s
186 if self.fragment:
191 if self.fragment:
187 s += '#' + self.fragment
192 s += '#' + self.fragment
188 return s
193 return s
189
194
190 s = self.scheme + ':'
195 s = self.scheme + ':'
191 if (self.user or self.passwd or self.host or
196 if (self.user or self.passwd or self.host or
192 self.scheme and not self.path):
197 self.scheme and not self.path):
193 s += '//'
198 s += '//'
194 if self.user:
199 if self.user:
195 s += urllib.quote(self.user, safe=self._safechars)
200 s += urllib.quote(self.user, safe=self._safechars)
196 if self.passwd:
201 if self.passwd:
197 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
202 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
198 if self.user or self.passwd:
203 if self.user or self.passwd:
199 s += '@'
204 s += '@'
200 if self.host:
205 if self.host:
201 if not (self.host.startswith('[') and self.host.endswith(']')):
206 if not (self.host.startswith('[') and self.host.endswith(']')):
202 s += urllib.quote(self.host)
207 s += urllib.quote(self.host)
203 else:
208 else:
204 s += self.host
209 s += self.host
205 if self.port:
210 if self.port:
206 s += ':' + urllib.quote(self.port)
211 s += ':' + urllib.quote(self.port)
207 if self.host:
212 if self.host:
208 s += '/'
213 s += '/'
209 if self.path:
214 if self.path:
210 s += urllib.quote(self.path, safe=self._safepchars)
215 s += urllib.quote(self.path, safe=self._safepchars)
211 if self.query:
216 if self.query:
212 s += '?' + urllib.quote(self.query, safe=self._safepchars)
217 s += '?' + urllib.quote(self.query, safe=self._safepchars)
213 if self.fragment is not None:
218 if self.fragment is not None:
214 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
219 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
215 return s
220 return s
216
221
217 def authinfo(self):
222 def authinfo(self):
218 user, passwd = self.user, self.passwd
223 user, passwd = self.user, self.passwd
219 try:
224 try:
220 self.user, self.passwd = None, None
225 self.user, self.passwd = None, None
221 s = str(self)
226 s = str(self)
222 finally:
227 finally:
223 self.user, self.passwd = user, passwd
228 self.user, self.passwd = user, passwd
224 if not self.user:
229 if not self.user:
225 return (s, None)
230 return (s, None)
226 return (s, (None, (str(self), self.host),
231 return (s, (None, (str(self), self.host),
227 self.user, self.passwd or ''))
232 self.user, self.passwd or ''))
228
233
229 def has_scheme(path):
234 def has_scheme(path):
230 return bool(url(path).scheme)
235 return bool(url(path).scheme)
231
236
232 def has_drive_letter(path):
237 def has_drive_letter(path):
233 return path[1:2] == ':' and path[0:1].isalpha()
238 return path[1:2] == ':' and path[0:1].isalpha()
234
239
235 def hidepassword(u):
240 def hidepassword(u):
236 '''hide user credential in a url string'''
241 '''hide user credential in a url string'''
237 u = url(u)
242 u = url(u)
238 if u.passwd:
243 if u.passwd:
239 u.passwd = '***'
244 u.passwd = '***'
240 return str(u)
245 return str(u)
241
246
242 def removeauth(u):
247 def removeauth(u):
243 '''remove all authentication information from a url string'''
248 '''remove all authentication information from a url string'''
244 u = url(u)
249 u = url(u)
245 u.user = u.passwd = None
250 u.user = u.passwd = None
246 return str(u)
251 return str(u)
247
252
248 def netlocsplit(netloc):
253 def netlocsplit(netloc):
249 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
254 '''split [user[:passwd]@]host[:port] into 4-tuple.'''
250
255
251 a = netloc.find('@')
256 a = netloc.find('@')
252 if a == -1:
257 if a == -1:
253 user, passwd = None, None
258 user, passwd = None, None
254 else:
259 else:
255 userpass, netloc = netloc[:a], netloc[a + 1:]
260 userpass, netloc = netloc[:a], netloc[a + 1:]
256 c = userpass.find(':')
261 c = userpass.find(':')
257 if c == -1:
262 if c == -1:
258 user, passwd = urllib.unquote(userpass), None
263 user, passwd = urllib.unquote(userpass), None
259 else:
264 else:
260 user = urllib.unquote(userpass[:c])
265 user = urllib.unquote(userpass[:c])
261 passwd = urllib.unquote(userpass[c + 1:])
266 passwd = urllib.unquote(userpass[c + 1:])
262 c = netloc.find(':')
267 c = netloc.find(':')
263 if c == -1:
268 if c == -1:
264 host, port = netloc, None
269 host, port = netloc, None
265 else:
270 else:
266 host, port = netloc[:c], netloc[c + 1:]
271 host, port = netloc[:c], netloc[c + 1:]
267 return host, port, user, passwd
272 return host, port, user, passwd
268
273
269 def netlocunsplit(host, port, user=None, passwd=None):
274 def netlocunsplit(host, port, user=None, passwd=None):
270 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
275 '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
271 if port:
276 if port:
272 hostport = host + ':' + port
277 hostport = host + ':' + port
273 else:
278 else:
274 hostport = host
279 hostport = host
275 if user:
280 if user:
276 quote = lambda s: urllib.quote(s, safe='')
281 quote = lambda s: urllib.quote(s, safe='')
277 if passwd:
282 if passwd:
278 userpass = quote(user) + ':' + quote(passwd)
283 userpass = quote(user) + ':' + quote(passwd)
279 else:
284 else:
280 userpass = quote(user)
285 userpass = quote(user)
281 return userpass + '@' + hostport
286 return userpass + '@' + hostport
282 return hostport
287 return hostport
283
288
284 def readauthforuri(ui, uri):
289 def readauthforuri(ui, uri):
285 # Read configuration
290 # Read configuration
286 config = dict()
291 config = dict()
287 for key, val in ui.configitems('auth'):
292 for key, val in ui.configitems('auth'):
288 if '.' not in key:
293 if '.' not in key:
289 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
294 ui.warn(_("ignoring invalid [auth] key '%s'\n") % key)
290 continue
295 continue
291 group, setting = key.rsplit('.', 1)
296 group, setting = key.rsplit('.', 1)
292 gdict = config.setdefault(group, dict())
297 gdict = config.setdefault(group, dict())
293 if setting in ('username', 'cert', 'key'):
298 if setting in ('username', 'cert', 'key'):
294 val = util.expandpath(val)
299 val = util.expandpath(val)
295 gdict[setting] = val
300 gdict[setting] = val
296
301
297 # Find the best match
302 # Find the best match
298 scheme, hostpath = uri.split('://', 1)
303 scheme, hostpath = uri.split('://', 1)
299 bestlen = 0
304 bestlen = 0
300 bestauth = None
305 bestauth = None
301 for group, auth in config.iteritems():
306 for group, auth in config.iteritems():
302 prefix = auth.get('prefix')
307 prefix = auth.get('prefix')
303 if not prefix:
308 if not prefix:
304 continue
309 continue
305 p = prefix.split('://', 1)
310 p = prefix.split('://', 1)
306 if len(p) > 1:
311 if len(p) > 1:
307 schemes, prefix = [p[0]], p[1]
312 schemes, prefix = [p[0]], p[1]
308 else:
313 else:
309 schemes = (auth.get('schemes') or 'https').split()
314 schemes = (auth.get('schemes') or 'https').split()
310 if (prefix == '*' or hostpath.startswith(prefix)) and \
315 if (prefix == '*' or hostpath.startswith(prefix)) and \
311 len(prefix) > bestlen and scheme in schemes:
316 len(prefix) > bestlen and scheme in schemes:
312 bestlen = len(prefix)
317 bestlen = len(prefix)
313 bestauth = group, auth
318 bestauth = group, auth
314 return bestauth
319 return bestauth
315
320
316 _safe = ('abcdefghijklmnopqrstuvwxyz'
321 _safe = ('abcdefghijklmnopqrstuvwxyz'
317 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
322 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
318 '0123456789' '_.-/')
323 '0123456789' '_.-/')
319 _safeset = None
324 _safeset = None
320 _hex = None
325 _hex = None
321 def quotepath(path):
326 def quotepath(path):
322 '''quote the path part of a URL
327 '''quote the path part of a URL
323
328
324 This is similar to urllib.quote, but it also tries to avoid
329 This is similar to urllib.quote, but it also tries to avoid
325 quoting things twice (inspired by wget):
330 quoting things twice (inspired by wget):
326
331
327 >>> quotepath('abc def')
332 >>> quotepath('abc def')
328 'abc%20def'
333 'abc%20def'
329 >>> quotepath('abc%20def')
334 >>> quotepath('abc%20def')
330 'abc%20def'
335 'abc%20def'
331 >>> quotepath('abc%20 def')
336 >>> quotepath('abc%20 def')
332 'abc%20%20def'
337 'abc%20%20def'
333 >>> quotepath('abc def%20')
338 >>> quotepath('abc def%20')
334 'abc%20def%20'
339 'abc%20def%20'
335 >>> quotepath('abc def%2')
340 >>> quotepath('abc def%2')
336 'abc%20def%252'
341 'abc%20def%252'
337 >>> quotepath('abc def%')
342 >>> quotepath('abc def%')
338 'abc%20def%25'
343 'abc%20def%25'
339 '''
344 '''
340 global _safeset, _hex
345 global _safeset, _hex
341 if _safeset is None:
346 if _safeset is None:
342 _safeset = set(_safe)
347 _safeset = set(_safe)
343 _hex = set('abcdefABCDEF0123456789')
348 _hex = set('abcdefABCDEF0123456789')
344 l = list(path)
349 l = list(path)
345 for i in xrange(len(l)):
350 for i in xrange(len(l)):
346 c = l[i]
351 c = l[i]
347 if (c == '%' and i + 2 < len(l) and
352 if (c == '%' and i + 2 < len(l) and
348 l[i + 1] in _hex and l[i + 2] in _hex):
353 l[i + 1] in _hex and l[i + 2] in _hex):
349 pass
354 pass
350 elif c not in _safeset:
355 elif c not in _safeset:
351 l[i] = '%%%02X' % ord(c)
356 l[i] = '%%%02X' % ord(c)
352 return ''.join(l)
357 return ''.join(l)
353
358
354 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
359 class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
355 def __init__(self, ui):
360 def __init__(self, ui):
356 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
361 urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
357 self.ui = ui
362 self.ui = ui
358
363
359 def find_user_password(self, realm, authuri):
364 def find_user_password(self, realm, authuri):
360 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
365 authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
361 self, realm, authuri)
366 self, realm, authuri)
362 user, passwd = authinfo
367 user, passwd = authinfo
363 if user and passwd:
368 if user and passwd:
364 self._writedebug(user, passwd)
369 self._writedebug(user, passwd)
365 return (user, passwd)
370 return (user, passwd)
366
371
367 if not user:
372 if not user:
368 res = readauthforuri(self.ui, authuri)
373 res = readauthforuri(self.ui, authuri)
369 if res:
374 if res:
370 group, auth = res
375 group, auth = res
371 user, passwd = auth.get('username'), auth.get('password')
376 user, passwd = auth.get('username'), auth.get('password')
372 self.ui.debug("using auth.%s.* for authentication\n" % group)
377 self.ui.debug("using auth.%s.* for authentication\n" % group)
373 if not user or not passwd:
378 if not user or not passwd:
374 if not self.ui.interactive():
379 if not self.ui.interactive():
375 raise util.Abort(_('http authorization required'))
380 raise util.Abort(_('http authorization required'))
376
381
377 self.ui.write(_("http authorization required\n"))
382 self.ui.write(_("http authorization required\n"))
378 self.ui.write(_("realm: %s\n") % realm)
383 self.ui.write(_("realm: %s\n") % realm)
379 if user:
384 if user:
380 self.ui.write(_("user: %s\n") % user)
385 self.ui.write(_("user: %s\n") % user)
381 else:
386 else:
382 user = self.ui.prompt(_("user:"), default=None)
387 user = self.ui.prompt(_("user:"), default=None)
383
388
384 if not passwd:
389 if not passwd:
385 passwd = self.ui.getpass()
390 passwd = self.ui.getpass()
386
391
387 self.add_password(realm, authuri, user, passwd)
392 self.add_password(realm, authuri, user, passwd)
388 self._writedebug(user, passwd)
393 self._writedebug(user, passwd)
389 return (user, passwd)
394 return (user, passwd)
390
395
391 def _writedebug(self, user, passwd):
396 def _writedebug(self, user, passwd):
392 msg = _('http auth: user %s, password %s\n')
397 msg = _('http auth: user %s, password %s\n')
393 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
398 self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set'))
394
399
395 class proxyhandler(urllib2.ProxyHandler):
400 class proxyhandler(urllib2.ProxyHandler):
396 def __init__(self, ui):
401 def __init__(self, ui):
397 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
402 proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
398 # XXX proxyauthinfo = None
403 # XXX proxyauthinfo = None
399
404
400 if proxyurl:
405 if proxyurl:
401 # proxy can be proper url or host[:port]
406 # proxy can be proper url or host[:port]
402 if not (proxyurl.startswith('http:') or
407 if not (proxyurl.startswith('http:') or
403 proxyurl.startswith('https:')):
408 proxyurl.startswith('https:')):
404 proxyurl = 'http://' + proxyurl + '/'
409 proxyurl = 'http://' + proxyurl + '/'
405 snpqf = urlparse.urlsplit(proxyurl)
410 snpqf = urlparse.urlsplit(proxyurl)
406 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
411 proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
407 hpup = netlocsplit(proxynetloc)
412 hpup = netlocsplit(proxynetloc)
408
413
409 proxyhost, proxyport, proxyuser, proxypasswd = hpup
414 proxyhost, proxyport, proxyuser, proxypasswd = hpup
410 if not proxyuser:
415 if not proxyuser:
411 proxyuser = ui.config("http_proxy", "user")
416 proxyuser = ui.config("http_proxy", "user")
412 proxypasswd = ui.config("http_proxy", "passwd")
417 proxypasswd = ui.config("http_proxy", "passwd")
413
418
414 # see if we should use a proxy for this url
419 # see if we should use a proxy for this url
415 no_list = ["localhost", "127.0.0.1"]
420 no_list = ["localhost", "127.0.0.1"]
416 no_list.extend([p.lower() for
421 no_list.extend([p.lower() for
417 p in ui.configlist("http_proxy", "no")])
422 p in ui.configlist("http_proxy", "no")])
418 no_list.extend([p.strip().lower() for
423 no_list.extend([p.strip().lower() for
419 p in os.getenv("no_proxy", '').split(',')
424 p in os.getenv("no_proxy", '').split(',')
420 if p.strip()])
425 if p.strip()])
421 # "http_proxy.always" config is for running tests on localhost
426 # "http_proxy.always" config is for running tests on localhost
422 if ui.configbool("http_proxy", "always"):
427 if ui.configbool("http_proxy", "always"):
423 self.no_list = []
428 self.no_list = []
424 else:
429 else:
425 self.no_list = no_list
430 self.no_list = no_list
426
431
427 proxyurl = urlparse.urlunsplit((
432 proxyurl = urlparse.urlunsplit((
428 proxyscheme, netlocunsplit(proxyhost, proxyport,
433 proxyscheme, netlocunsplit(proxyhost, proxyport,
429 proxyuser, proxypasswd or ''),
434 proxyuser, proxypasswd or ''),
430 proxypath, proxyquery, proxyfrag))
435 proxypath, proxyquery, proxyfrag))
431 proxies = {'http': proxyurl, 'https': proxyurl}
436 proxies = {'http': proxyurl, 'https': proxyurl}
432 ui.debug('proxying through http://%s:%s\n' %
437 ui.debug('proxying through http://%s:%s\n' %
433 (proxyhost, proxyport))
438 (proxyhost, proxyport))
434 else:
439 else:
435 proxies = {}
440 proxies = {}
436
441
437 # urllib2 takes proxy values from the environment and those
442 # urllib2 takes proxy values from the environment and those
438 # will take precedence if found, so drop them
443 # will take precedence if found, so drop them
439 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
444 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
440 try:
445 try:
441 if env in os.environ:
446 if env in os.environ:
442 del os.environ[env]
447 del os.environ[env]
443 except OSError:
448 except OSError:
444 pass
449 pass
445
450
446 urllib2.ProxyHandler.__init__(self, proxies)
451 urllib2.ProxyHandler.__init__(self, proxies)
447 self.ui = ui
452 self.ui = ui
448
453
449 def proxy_open(self, req, proxy, type_):
454 def proxy_open(self, req, proxy, type_):
450 host = req.get_host().split(':')[0]
455 host = req.get_host().split(':')[0]
451 if host in self.no_list:
456 if host in self.no_list:
452 return None
457 return None
453
458
454 # work around a bug in Python < 2.4.2
459 # work around a bug in Python < 2.4.2
455 # (it leaves a "\n" at the end of Proxy-authorization headers)
460 # (it leaves a "\n" at the end of Proxy-authorization headers)
456 baseclass = req.__class__
461 baseclass = req.__class__
457 class _request(baseclass):
462 class _request(baseclass):
458 def add_header(self, key, val):
463 def add_header(self, key, val):
459 if key.lower() == 'proxy-authorization':
464 if key.lower() == 'proxy-authorization':
460 val = val.strip()
465 val = val.strip()
461 return baseclass.add_header(self, key, val)
466 return baseclass.add_header(self, key, val)
462 req.__class__ = _request
467 req.__class__ = _request
463
468
464 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
469 return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
465
470
466 class httpsendfile(object):
471 class httpsendfile(object):
467 """This is a wrapper around the objects returned by python's "open".
472 """This is a wrapper around the objects returned by python's "open".
468
473
469 Its purpose is to send file-like objects via HTTP and, to do so, it
474 Its purpose is to send file-like objects via HTTP and, to do so, it
470 defines a __len__ attribute to feed the Content-Length header.
475 defines a __len__ attribute to feed the Content-Length header.
471 """
476 """
472
477
473 def __init__(self, ui, *args, **kwargs):
478 def __init__(self, ui, *args, **kwargs):
474 # We can't just "self._data = open(*args, **kwargs)" here because there
479 # We can't just "self._data = open(*args, **kwargs)" here because there
475 # is an "open" function defined in this module that shadows the global
480 # is an "open" function defined in this module that shadows the global
476 # one
481 # one
477 self.ui = ui
482 self.ui = ui
478 self._data = __builtin__.open(*args, **kwargs)
483 self._data = __builtin__.open(*args, **kwargs)
479 self.seek = self._data.seek
484 self.seek = self._data.seek
480 self.close = self._data.close
485 self.close = self._data.close
481 self.write = self._data.write
486 self.write = self._data.write
482 self._len = os.fstat(self._data.fileno()).st_size
487 self._len = os.fstat(self._data.fileno()).st_size
483 self._pos = 0
488 self._pos = 0
484 self._total = len(self) / 1024 * 2
489 self._total = len(self) / 1024 * 2
485
490
486 def read(self, *args, **kwargs):
491 def read(self, *args, **kwargs):
487 try:
492 try:
488 ret = self._data.read(*args, **kwargs)
493 ret = self._data.read(*args, **kwargs)
489 except EOFError:
494 except EOFError:
490 self.ui.progress(_('sending'), None)
495 self.ui.progress(_('sending'), None)
491 self._pos += len(ret)
496 self._pos += len(ret)
492 # We pass double the max for total because we currently have
497 # We pass double the max for total because we currently have
493 # to send the bundle twice in the case of a server that
498 # to send the bundle twice in the case of a server that
494 # requires authentication. Since we can't know until we try
499 # requires authentication. Since we can't know until we try
495 # once whether authentication will be required, just lie to
500 # once whether authentication will be required, just lie to
496 # the user and maybe the push succeeds suddenly at 50%.
501 # the user and maybe the push succeeds suddenly at 50%.
497 self.ui.progress(_('sending'), self._pos / 1024,
502 self.ui.progress(_('sending'), self._pos / 1024,
498 unit=_('kb'), total=self._total)
503 unit=_('kb'), total=self._total)
499 return ret
504 return ret
500
505
501 def __len__(self):
506 def __len__(self):
502 return self._len
507 return self._len
503
508
504 def _gen_sendfile(orgsend):
509 def _gen_sendfile(orgsend):
505 def _sendfile(self, data):
510 def _sendfile(self, data):
506 # send a file
511 # send a file
507 if isinstance(data, httpsendfile):
512 if isinstance(data, httpsendfile):
508 # if auth required, some data sent twice, so rewind here
513 # if auth required, some data sent twice, so rewind here
509 data.seek(0)
514 data.seek(0)
510 for chunk in util.filechunkiter(data):
515 for chunk in util.filechunkiter(data):
511 orgsend(self, chunk)
516 orgsend(self, chunk)
512 else:
517 else:
513 orgsend(self, data)
518 orgsend(self, data)
514 return _sendfile
519 return _sendfile
515
520
516 has_https = hasattr(urllib2, 'HTTPSHandler')
521 has_https = hasattr(urllib2, 'HTTPSHandler')
517 if has_https:
522 if has_https:
518 try:
523 try:
519 # avoid using deprecated/broken FakeSocket in python 2.6
524 # avoid using deprecated/broken FakeSocket in python 2.6
520 import ssl
525 import ssl
521 _ssl_wrap_socket = ssl.wrap_socket
526 _ssl_wrap_socket = ssl.wrap_socket
522 CERT_REQUIRED = ssl.CERT_REQUIRED
527 CERT_REQUIRED = ssl.CERT_REQUIRED
523 except ImportError:
528 except ImportError:
524 CERT_REQUIRED = 2
529 CERT_REQUIRED = 2
525
530
526 def _ssl_wrap_socket(sock, key_file, cert_file,
531 def _ssl_wrap_socket(sock, key_file, cert_file,
527 cert_reqs=CERT_REQUIRED, ca_certs=None):
532 cert_reqs=CERT_REQUIRED, ca_certs=None):
528 if ca_certs:
533 if ca_certs:
529 raise util.Abort(_(
534 raise util.Abort(_(
530 'certificate checking requires Python 2.6'))
535 'certificate checking requires Python 2.6'))
531
536
532 ssl = socket.ssl(sock, key_file, cert_file)
537 ssl = socket.ssl(sock, key_file, cert_file)
533 return httplib.FakeSocket(sock, ssl)
538 return httplib.FakeSocket(sock, ssl)
534
539
535 try:
540 try:
536 _create_connection = socket.create_connection
541 _create_connection = socket.create_connection
537 except AttributeError:
542 except AttributeError:
538 _GLOBAL_DEFAULT_TIMEOUT = object()
543 _GLOBAL_DEFAULT_TIMEOUT = object()
539
544
540 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
545 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
541 source_address=None):
546 source_address=None):
542 # lifted from Python 2.6
547 # lifted from Python 2.6
543
548
544 msg = "getaddrinfo returns an empty list"
549 msg = "getaddrinfo returns an empty list"
545 host, port = address
550 host, port = address
546 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
551 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
547 af, socktype, proto, canonname, sa = res
552 af, socktype, proto, canonname, sa = res
548 sock = None
553 sock = None
549 try:
554 try:
550 sock = socket.socket(af, socktype, proto)
555 sock = socket.socket(af, socktype, proto)
551 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
556 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
552 sock.settimeout(timeout)
557 sock.settimeout(timeout)
553 if source_address:
558 if source_address:
554 sock.bind(source_address)
559 sock.bind(source_address)
555 sock.connect(sa)
560 sock.connect(sa)
556 return sock
561 return sock
557
562
558 except socket.error, msg:
563 except socket.error, msg:
559 if sock is not None:
564 if sock is not None:
560 sock.close()
565 sock.close()
561
566
562 raise socket.error, msg
567 raise socket.error, msg
563
568
564 class httpconnection(keepalive.HTTPConnection):
569 class httpconnection(keepalive.HTTPConnection):
565 # must be able to send big bundle as stream.
570 # must be able to send big bundle as stream.
566 send = _gen_sendfile(keepalive.HTTPConnection.send)
571 send = _gen_sendfile(keepalive.HTTPConnection.send)
567
572
568 def connect(self):
573 def connect(self):
569 if has_https and self.realhostport: # use CONNECT proxy
574 if has_https and self.realhostport: # use CONNECT proxy
570 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
575 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
571 self.sock.connect((self.host, self.port))
576 self.sock.connect((self.host, self.port))
572 if _generic_proxytunnel(self):
577 if _generic_proxytunnel(self):
573 # we do not support client x509 certificates
578 # we do not support client x509 certificates
574 self.sock = _ssl_wrap_socket(self.sock, None, None)
579 self.sock = _ssl_wrap_socket(self.sock, None, None)
575 else:
580 else:
576 keepalive.HTTPConnection.connect(self)
581 keepalive.HTTPConnection.connect(self)
577
582
578 def getresponse(self):
583 def getresponse(self):
579 proxyres = getattr(self, 'proxyres', None)
584 proxyres = getattr(self, 'proxyres', None)
580 if proxyres:
585 if proxyres:
581 if proxyres.will_close:
586 if proxyres.will_close:
582 self.close()
587 self.close()
583 self.proxyres = None
588 self.proxyres = None
584 return proxyres
589 return proxyres
585 return keepalive.HTTPConnection.getresponse(self)
590 return keepalive.HTTPConnection.getresponse(self)
586
591
587 # general transaction handler to support different ways to handle
592 # general transaction handler to support different ways to handle
588 # HTTPS proxying before and after Python 2.6.3.
593 # HTTPS proxying before and after Python 2.6.3.
589 def _generic_start_transaction(handler, h, req):
594 def _generic_start_transaction(handler, h, req):
590 if hasattr(req, '_tunnel_host') and req._tunnel_host:
595 if hasattr(req, '_tunnel_host') and req._tunnel_host:
591 tunnel_host = req._tunnel_host
596 tunnel_host = req._tunnel_host
592 if tunnel_host[:7] not in ['http://', 'https:/']:
597 if tunnel_host[:7] not in ['http://', 'https:/']:
593 tunnel_host = 'https://' + tunnel_host
598 tunnel_host = 'https://' + tunnel_host
594 new_tunnel = True
599 new_tunnel = True
595 else:
600 else:
596 tunnel_host = req.get_selector()
601 tunnel_host = req.get_selector()
597 new_tunnel = False
602 new_tunnel = False
598
603
599 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
604 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
600 urlparts = urlparse.urlparse(tunnel_host)
605 urlparts = urlparse.urlparse(tunnel_host)
601 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
606 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
602 realhostport = urlparts[1]
607 realhostport = urlparts[1]
603 if realhostport[-1] == ']' or ':' not in realhostport:
608 if realhostport[-1] == ']' or ':' not in realhostport:
604 realhostport += ':443'
609 realhostport += ':443'
605
610
606 h.realhostport = realhostport
611 h.realhostport = realhostport
607 h.headers = req.headers.copy()
612 h.headers = req.headers.copy()
608 h.headers.update(handler.parent.addheaders)
613 h.headers.update(handler.parent.addheaders)
609 return
614 return
610
615
611 h.realhostport = None
616 h.realhostport = None
612 h.headers = None
617 h.headers = None
613
618
614 def _generic_proxytunnel(self):
619 def _generic_proxytunnel(self):
615 proxyheaders = dict(
620 proxyheaders = dict(
616 [(x, self.headers[x]) for x in self.headers
621 [(x, self.headers[x]) for x in self.headers
617 if x.lower().startswith('proxy-')])
622 if x.lower().startswith('proxy-')])
618 self._set_hostport(self.host, self.port)
623 self._set_hostport(self.host, self.port)
619 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
624 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
620 for header in proxyheaders.iteritems():
625 for header in proxyheaders.iteritems():
621 self.send('%s: %s\r\n' % header)
626 self.send('%s: %s\r\n' % header)
622 self.send('\r\n')
627 self.send('\r\n')
623
628
624 # majority of the following code is duplicated from
629 # majority of the following code is duplicated from
625 # httplib.HTTPConnection as there are no adequate places to
630 # httplib.HTTPConnection as there are no adequate places to
626 # override functions to provide the needed functionality
631 # override functions to provide the needed functionality
627 res = self.response_class(self.sock,
632 res = self.response_class(self.sock,
628 strict=self.strict,
633 strict=self.strict,
629 method=self._method)
634 method=self._method)
630
635
631 while True:
636 while True:
632 version, status, reason = res._read_status()
637 version, status, reason = res._read_status()
633 if status != httplib.CONTINUE:
638 if status != httplib.CONTINUE:
634 break
639 break
635 while True:
640 while True:
636 skip = res.fp.readline().strip()
641 skip = res.fp.readline().strip()
637 if not skip:
642 if not skip:
638 break
643 break
639 res.status = status
644 res.status = status
640 res.reason = reason.strip()
645 res.reason = reason.strip()
641
646
642 if res.status == 200:
647 if res.status == 200:
643 while True:
648 while True:
644 line = res.fp.readline()
649 line = res.fp.readline()
645 if line == '\r\n':
650 if line == '\r\n':
646 break
651 break
647 return True
652 return True
648
653
649 if version == 'HTTP/1.0':
654 if version == 'HTTP/1.0':
650 res.version = 10
655 res.version = 10
651 elif version.startswith('HTTP/1.'):
656 elif version.startswith('HTTP/1.'):
652 res.version = 11
657 res.version = 11
653 elif version == 'HTTP/0.9':
658 elif version == 'HTTP/0.9':
654 res.version = 9
659 res.version = 9
655 else:
660 else:
656 raise httplib.UnknownProtocol(version)
661 raise httplib.UnknownProtocol(version)
657
662
658 if res.version == 9:
663 if res.version == 9:
659 res.length = None
664 res.length = None
660 res.chunked = 0
665 res.chunked = 0
661 res.will_close = 1
666 res.will_close = 1
662 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
667 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
663 return False
668 return False
664
669
665 res.msg = httplib.HTTPMessage(res.fp)
670 res.msg = httplib.HTTPMessage(res.fp)
666 res.msg.fp = None
671 res.msg.fp = None
667
672
668 # are we using the chunked-style of transfer encoding?
673 # are we using the chunked-style of transfer encoding?
669 trenc = res.msg.getheader('transfer-encoding')
674 trenc = res.msg.getheader('transfer-encoding')
670 if trenc and trenc.lower() == "chunked":
675 if trenc and trenc.lower() == "chunked":
671 res.chunked = 1
676 res.chunked = 1
672 res.chunk_left = None
677 res.chunk_left = None
673 else:
678 else:
674 res.chunked = 0
679 res.chunked = 0
675
680
676 # will the connection close at the end of the response?
681 # will the connection close at the end of the response?
677 res.will_close = res._check_close()
682 res.will_close = res._check_close()
678
683
679 # do we have a Content-Length?
684 # do we have a Content-Length?
680 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
685 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
681 length = res.msg.getheader('content-length')
686 length = res.msg.getheader('content-length')
682 if length and not res.chunked:
687 if length and not res.chunked:
683 try:
688 try:
684 res.length = int(length)
689 res.length = int(length)
685 except ValueError:
690 except ValueError:
686 res.length = None
691 res.length = None
687 else:
692 else:
688 if res.length < 0: # ignore nonsensical negative lengths
693 if res.length < 0: # ignore nonsensical negative lengths
689 res.length = None
694 res.length = None
690 else:
695 else:
691 res.length = None
696 res.length = None
692
697
693 # does the body have a fixed length? (of zero)
698 # does the body have a fixed length? (of zero)
694 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
699 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
695 100 <= status < 200 or # 1xx codes
700 100 <= status < 200 or # 1xx codes
696 res._method == 'HEAD'):
701 res._method == 'HEAD'):
697 res.length = 0
702 res.length = 0
698
703
699 # if the connection remains open, and we aren't using chunked, and
704 # if the connection remains open, and we aren't using chunked, and
700 # a content-length was not provided, then assume that the connection
705 # a content-length was not provided, then assume that the connection
701 # WILL close.
706 # WILL close.
702 if (not res.will_close and
707 if (not res.will_close and
703 not res.chunked and
708 not res.chunked and
704 res.length is None):
709 res.length is None):
705 res.will_close = 1
710 res.will_close = 1
706
711
707 self.proxyres = res
712 self.proxyres = res
708
713
709 return False
714 return False
710
715
711 class httphandler(keepalive.HTTPHandler):
716 class httphandler(keepalive.HTTPHandler):
712 def http_open(self, req):
717 def http_open(self, req):
713 return self.do_open(httpconnection, req)
718 return self.do_open(httpconnection, req)
714
719
715 def _start_transaction(self, h, req):
720 def _start_transaction(self, h, req):
716 _generic_start_transaction(self, h, req)
721 _generic_start_transaction(self, h, req)
717 return keepalive.HTTPHandler._start_transaction(self, h, req)
722 return keepalive.HTTPHandler._start_transaction(self, h, req)
718
723
719 def _verifycert(cert, hostname):
724 def _verifycert(cert, hostname):
720 '''Verify that cert (in socket.getpeercert() format) matches hostname.
725 '''Verify that cert (in socket.getpeercert() format) matches hostname.
721 CRLs is not handled.
726 CRLs is not handled.
722
727
723 Returns error message if any problems are found and None on success.
728 Returns error message if any problems are found and None on success.
724 '''
729 '''
725 if not cert:
730 if not cert:
726 return _('no certificate received')
731 return _('no certificate received')
727 dnsname = hostname.lower()
732 dnsname = hostname.lower()
728 def matchdnsname(certname):
733 def matchdnsname(certname):
729 return (certname == dnsname or
734 return (certname == dnsname or
730 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
735 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1])
731
736
732 san = cert.get('subjectAltName', [])
737 san = cert.get('subjectAltName', [])
733 if san:
738 if san:
734 certnames = [value.lower() for key, value in san if key == 'DNS']
739 certnames = [value.lower() for key, value in san if key == 'DNS']
735 for name in certnames:
740 for name in certnames:
736 if matchdnsname(name):
741 if matchdnsname(name):
737 return None
742 return None
738 return _('certificate is for %s') % ', '.join(certnames)
743 return _('certificate is for %s') % ', '.join(certnames)
739
744
740 # subject is only checked when subjectAltName is empty
745 # subject is only checked when subjectAltName is empty
741 for s in cert.get('subject', []):
746 for s in cert.get('subject', []):
742 key, value = s[0]
747 key, value = s[0]
743 if key == 'commonName':
748 if key == 'commonName':
744 try:
749 try:
745 # 'subject' entries are unicode
750 # 'subject' entries are unicode
746 certname = value.lower().encode('ascii')
751 certname = value.lower().encode('ascii')
747 except UnicodeEncodeError:
752 except UnicodeEncodeError:
748 return _('IDN in certificate not supported')
753 return _('IDN in certificate not supported')
749 if matchdnsname(certname):
754 if matchdnsname(certname):
750 return None
755 return None
751 return _('certificate is for %s') % certname
756 return _('certificate is for %s') % certname
752 return _('no commonName or subjectAltName found in certificate')
757 return _('no commonName or subjectAltName found in certificate')
753
758
754 if has_https:
759 if has_https:
755 class httpsconnection(httplib.HTTPSConnection):
760 class httpsconnection(httplib.HTTPSConnection):
756 response_class = keepalive.HTTPResponse
761 response_class = keepalive.HTTPResponse
757 # must be able to send big bundle as stream.
762 # must be able to send big bundle as stream.
758 send = _gen_sendfile(keepalive.safesend)
763 send = _gen_sendfile(keepalive.safesend)
759 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
764 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
760
765
761 def connect(self):
766 def connect(self):
762 self.sock = _create_connection((self.host, self.port))
767 self.sock = _create_connection((self.host, self.port))
763
768
764 host = self.host
769 host = self.host
765 if self.realhostport: # use CONNECT proxy
770 if self.realhostport: # use CONNECT proxy
766 something = _generic_proxytunnel(self)
771 something = _generic_proxytunnel(self)
767 host = self.realhostport.rsplit(':', 1)[0]
772 host = self.realhostport.rsplit(':', 1)[0]
768
773
769 cacerts = self.ui.config('web', 'cacerts')
774 cacerts = self.ui.config('web', 'cacerts')
770 hostfingerprint = self.ui.config('hostfingerprints', host)
775 hostfingerprint = self.ui.config('hostfingerprints', host)
771
776
772 if cacerts and not hostfingerprint:
777 if cacerts and not hostfingerprint:
773 cacerts = util.expandpath(cacerts)
778 cacerts = util.expandpath(cacerts)
774 if not os.path.exists(cacerts):
779 if not os.path.exists(cacerts):
775 raise util.Abort(_('could not find '
780 raise util.Abort(_('could not find '
776 'web.cacerts: %s') % cacerts)
781 'web.cacerts: %s') % cacerts)
777 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
782 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
778 self.cert_file, cert_reqs=CERT_REQUIRED,
783 self.cert_file, cert_reqs=CERT_REQUIRED,
779 ca_certs=cacerts)
784 ca_certs=cacerts)
780 msg = _verifycert(self.sock.getpeercert(), host)
785 msg = _verifycert(self.sock.getpeercert(), host)
781 if msg:
786 if msg:
782 raise util.Abort(_('%s certificate error: %s '
787 raise util.Abort(_('%s certificate error: %s '
783 '(use --insecure to connect '
788 '(use --insecure to connect '
784 'insecurely)') % (host, msg))
789 'insecurely)') % (host, msg))
785 self.ui.debug('%s certificate successfully verified\n' % host)
790 self.ui.debug('%s certificate successfully verified\n' % host)
786 else:
791 else:
787 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
792 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
788 self.cert_file)
793 self.cert_file)
789 if hasattr(self.sock, 'getpeercert'):
794 if hasattr(self.sock, 'getpeercert'):
790 peercert = self.sock.getpeercert(True)
795 peercert = self.sock.getpeercert(True)
791 peerfingerprint = util.sha1(peercert).hexdigest()
796 peerfingerprint = util.sha1(peercert).hexdigest()
792 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
797 nicefingerprint = ":".join([peerfingerprint[x:x + 2]
793 for x in xrange(0, len(peerfingerprint), 2)])
798 for x in xrange(0, len(peerfingerprint), 2)])
794 if hostfingerprint:
799 if hostfingerprint:
795 if peerfingerprint.lower() != \
800 if peerfingerprint.lower() != \
796 hostfingerprint.replace(':', '').lower():
801 hostfingerprint.replace(':', '').lower():
797 raise util.Abort(_('invalid certificate for %s '
802 raise util.Abort(_('invalid certificate for %s '
798 'with fingerprint %s') %
803 'with fingerprint %s') %
799 (host, nicefingerprint))
804 (host, nicefingerprint))
800 self.ui.debug('%s certificate matched fingerprint %s\n' %
805 self.ui.debug('%s certificate matched fingerprint %s\n' %
801 (host, nicefingerprint))
806 (host, nicefingerprint))
802 else:
807 else:
803 self.ui.warn(_('warning: %s certificate '
808 self.ui.warn(_('warning: %s certificate '
804 'with fingerprint %s not verified '
809 'with fingerprint %s not verified '
805 '(check hostfingerprints or web.cacerts '
810 '(check hostfingerprints or web.cacerts '
806 'config setting)\n') %
811 'config setting)\n') %
807 (host, nicefingerprint))
812 (host, nicefingerprint))
808 else: # python 2.5 ?
813 else: # python 2.5 ?
809 if hostfingerprint:
814 if hostfingerprint:
810 raise util.Abort(_('no certificate for %s with '
815 raise util.Abort(_('no certificate for %s with '
811 'configured hostfingerprint') % host)
816 'configured hostfingerprint') % host)
812 self.ui.warn(_('warning: %s certificate not verified '
817 self.ui.warn(_('warning: %s certificate not verified '
813 '(check web.cacerts config setting)\n') %
818 '(check web.cacerts config setting)\n') %
814 host)
819 host)
815
820
816 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
821 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
817 def __init__(self, ui):
822 def __init__(self, ui):
818 keepalive.KeepAliveHandler.__init__(self)
823 keepalive.KeepAliveHandler.__init__(self)
819 urllib2.HTTPSHandler.__init__(self)
824 urllib2.HTTPSHandler.__init__(self)
820 self.ui = ui
825 self.ui = ui
821 self.pwmgr = passwordmgr(self.ui)
826 self.pwmgr = passwordmgr(self.ui)
822
827
823 def _start_transaction(self, h, req):
828 def _start_transaction(self, h, req):
824 _generic_start_transaction(self, h, req)
829 _generic_start_transaction(self, h, req)
825 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
830 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
826
831
827 def https_open(self, req):
832 def https_open(self, req):
828 res = readauthforuri(self.ui, req.get_full_url())
833 res = readauthforuri(self.ui, req.get_full_url())
829 if res:
834 if res:
830 group, auth = res
835 group, auth = res
831 self.auth = auth
836 self.auth = auth
832 self.ui.debug("using auth.%s.* for authentication\n" % group)
837 self.ui.debug("using auth.%s.* for authentication\n" % group)
833 else:
838 else:
834 self.auth = None
839 self.auth = None
835 return self.do_open(self._makeconnection, req)
840 return self.do_open(self._makeconnection, req)
836
841
837 def _makeconnection(self, host, port=None, *args, **kwargs):
842 def _makeconnection(self, host, port=None, *args, **kwargs):
838 keyfile = None
843 keyfile = None
839 certfile = None
844 certfile = None
840
845
841 if len(args) >= 1: # key_file
846 if len(args) >= 1: # key_file
842 keyfile = args[0]
847 keyfile = args[0]
843 if len(args) >= 2: # cert_file
848 if len(args) >= 2: # cert_file
844 certfile = args[1]
849 certfile = args[1]
845 args = args[2:]
850 args = args[2:]
846
851
847 # if the user has specified different key/cert files in
852 # if the user has specified different key/cert files in
848 # hgrc, we prefer these
853 # hgrc, we prefer these
849 if self.auth and 'key' in self.auth and 'cert' in self.auth:
854 if self.auth and 'key' in self.auth and 'cert' in self.auth:
850 keyfile = self.auth['key']
855 keyfile = self.auth['key']
851 certfile = self.auth['cert']
856 certfile = self.auth['cert']
852
857
853 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
858 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
854 conn.ui = self.ui
859 conn.ui = self.ui
855 return conn
860 return conn
856
861
857 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
862 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
858 def __init__(self, *args, **kwargs):
863 def __init__(self, *args, **kwargs):
859 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
864 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
860 self.retried_req = None
865 self.retried_req = None
861
866
862 def reset_retry_count(self):
867 def reset_retry_count(self):
863 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
868 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
864 # forever. We disable reset_retry_count completely and reset in
869 # forever. We disable reset_retry_count completely and reset in
865 # http_error_auth_reqed instead.
870 # http_error_auth_reqed instead.
866 pass
871 pass
867
872
868 def http_error_auth_reqed(self, auth_header, host, req, headers):
873 def http_error_auth_reqed(self, auth_header, host, req, headers):
869 # Reset the retry counter once for each request.
874 # Reset the retry counter once for each request.
870 if req is not self.retried_req:
875 if req is not self.retried_req:
871 self.retried_req = req
876 self.retried_req = req
872 self.retried = 0
877 self.retried = 0
873 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
878 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
874 # it doesn't know about the auth type requested. This can happen if
879 # it doesn't know about the auth type requested. This can happen if
875 # somebody is using BasicAuth and types a bad password.
880 # somebody is using BasicAuth and types a bad password.
876 try:
881 try:
877 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
882 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
878 self, auth_header, host, req, headers)
883 self, auth_header, host, req, headers)
879 except ValueError, inst:
884 except ValueError, inst:
880 arg = inst.args[0]
885 arg = inst.args[0]
881 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
886 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
882 return
887 return
883 raise
888 raise
884
889
885 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
890 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
886 def __init__(self, *args, **kwargs):
891 def __init__(self, *args, **kwargs):
887 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
892 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
888 self.retried_req = None
893 self.retried_req = None
889
894
890 def reset_retry_count(self):
895 def reset_retry_count(self):
891 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
896 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
892 # forever. We disable reset_retry_count completely and reset in
897 # forever. We disable reset_retry_count completely and reset in
893 # http_error_auth_reqed instead.
898 # http_error_auth_reqed instead.
894 pass
899 pass
895
900
896 def http_error_auth_reqed(self, auth_header, host, req, headers):
901 def http_error_auth_reqed(self, auth_header, host, req, headers):
897 # Reset the retry counter once for each request.
902 # Reset the retry counter once for each request.
898 if req is not self.retried_req:
903 if req is not self.retried_req:
899 self.retried_req = req
904 self.retried_req = req
900 self.retried = 0
905 self.retried = 0
901 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
906 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
902 self, auth_header, host, req, headers)
907 self, auth_header, host, req, headers)
903
908
904 def getauthinfo(path):
909 def getauthinfo(path):
905 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
910 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
906 if not urlpath:
911 if not urlpath:
907 urlpath = '/'
912 urlpath = '/'
908 if scheme != 'file':
913 if scheme != 'file':
909 # XXX: why are we quoting the path again with some smart
914 # XXX: why are we quoting the path again with some smart
910 # heuristic here? Anyway, it cannot be done with file://
915 # heuristic here? Anyway, it cannot be done with file://
911 # urls since path encoding is os/fs dependent (see
916 # urls since path encoding is os/fs dependent (see
912 # urllib.pathname2url() for details).
917 # urllib.pathname2url() for details).
913 urlpath = quotepath(urlpath)
918 urlpath = quotepath(urlpath)
914 host, port, user, passwd = netlocsplit(netloc)
919 host, port, user, passwd = netlocsplit(netloc)
915
920
916 # urllib cannot handle URLs with embedded user or passwd
921 # urllib cannot handle URLs with embedded user or passwd
917 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
922 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
918 urlpath, query, frag))
923 urlpath, query, frag))
919 if user:
924 if user:
920 netloc = host
925 netloc = host
921 if port:
926 if port:
922 netloc += ':' + port
927 netloc += ':' + port
923 # Python < 2.4.3 uses only the netloc to search for a password
928 # Python < 2.4.3 uses only the netloc to search for a password
924 authinfo = (None, (url, netloc), user, passwd or '')
929 authinfo = (None, (url, netloc), user, passwd or '')
925 else:
930 else:
926 authinfo = None
931 authinfo = None
927 return url, authinfo
932 return url, authinfo
928
933
929 handlerfuncs = []
934 handlerfuncs = []
930
935
931 def opener(ui, authinfo=None):
936 def opener(ui, authinfo=None):
932 '''
937 '''
933 construct an opener suitable for urllib2
938 construct an opener suitable for urllib2
934 authinfo will be added to the password manager
939 authinfo will be added to the password manager
935 '''
940 '''
936 handlers = [httphandler()]
941 handlers = [httphandler()]
937 if has_https:
942 if has_https:
938 handlers.append(httpshandler(ui))
943 handlers.append(httpshandler(ui))
939
944
940 handlers.append(proxyhandler(ui))
945 handlers.append(proxyhandler(ui))
941
946
942 passmgr = passwordmgr(ui)
947 passmgr = passwordmgr(ui)
943 if authinfo is not None:
948 if authinfo is not None:
944 passmgr.add_password(*authinfo)
949 passmgr.add_password(*authinfo)
945 user, passwd = authinfo[2:4]
950 user, passwd = authinfo[2:4]
946 ui.debug('http auth: user %s, password %s\n' %
951 ui.debug('http auth: user %s, password %s\n' %
947 (user, passwd and '*' * len(passwd) or 'not set'))
952 (user, passwd and '*' * len(passwd) or 'not set'))
948
953
949 handlers.extend((httpbasicauthhandler(passmgr),
954 handlers.extend((httpbasicauthhandler(passmgr),
950 httpdigestauthhandler(passmgr)))
955 httpdigestauthhandler(passmgr)))
951 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
956 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
952 opener = urllib2.build_opener(*handlers)
957 opener = urllib2.build_opener(*handlers)
953
958
954 # 1.0 here is the _protocol_ version
959 # 1.0 here is the _protocol_ version
955 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
960 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
956 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
961 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
957 return opener
962 return opener
958
963
959 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
964 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
960
965
961 def open(ui, url, data=None):
966 def open(ui, url, data=None):
962 scheme = None
967 scheme = None
963 m = scheme_re.search(url)
968 m = scheme_re.search(url)
964 if m:
969 if m:
965 scheme = m.group(1).lower()
970 scheme = m.group(1).lower()
966 if not scheme:
971 if not scheme:
967 path = util.normpath(os.path.abspath(url))
972 path = util.normpath(os.path.abspath(url))
968 url = 'file://' + urllib.pathname2url(path)
973 url = 'file://' + urllib.pathname2url(path)
969 authinfo = None
974 authinfo = None
970 else:
975 else:
971 url, authinfo = getauthinfo(url)
976 url, authinfo = getauthinfo(url)
972 return opener(ui, authinfo).open(url, data)
977 return opener(ui, authinfo).open(url, data)
@@ -1,81 +1,85 b''
1 $ mkdir test
1 $ mkdir test
2 $ cd test
2 $ cd test
3
3
4 $ echo foo>foo
4 $ echo foo>foo
5 $ hg init
5 $ hg init
6 $ hg addremove
6 $ hg addremove
7 adding foo
7 adding foo
8 $ hg commit -m 1
8 $ hg commit -m 1
9
9
10 $ hg verify
10 $ hg verify
11 checking changesets
11 checking changesets
12 checking manifests
12 checking manifests
13 crosschecking files in changesets and manifests
13 crosschecking files in changesets and manifests
14 checking files
14 checking files
15 1 files, 1 changesets, 1 total revisions
15 1 files, 1 changesets, 1 total revisions
16
16
17 $ hg serve -p $HGPORT -d --pid-file=hg.pid
17 $ hg serve -p $HGPORT -d --pid-file=hg.pid
18 $ cat hg.pid >> $DAEMON_PIDS
18 $ cat hg.pid >> $DAEMON_PIDS
19 $ cd ..
19 $ cd ..
20
20
21 $ hg clone --pull http://foo:bar@localhost:$HGPORT/ copy
21 $ hg clone --pull http://foo:bar@localhost:$HGPORT/ copy
22 requesting all changes
22 requesting all changes
23 adding changesets
23 adding changesets
24 adding manifests
24 adding manifests
25 adding file changes
25 adding file changes
26 added 1 changesets with 1 changes to 1 files
26 added 1 changesets with 1 changes to 1 files
27 updating to branch default
27 updating to branch default
28 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29
29
30 $ cd copy
30 $ cd copy
31 $ hg verify
31 $ hg verify
32 checking changesets
32 checking changesets
33 checking manifests
33 checking manifests
34 crosschecking files in changesets and manifests
34 crosschecking files in changesets and manifests
35 checking files
35 checking files
36 1 files, 1 changesets, 1 total revisions
36 1 files, 1 changesets, 1 total revisions
37
37
38 $ hg co
38 $ hg co
39 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
39 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 $ cat foo
40 $ cat foo
41 foo
41 foo
42
42
43 $ hg manifest --debug
43 $ hg manifest --debug
44 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo
44 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo
45
45
46 $ hg pull
46 $ hg pull
47 pulling from http://foo:***@localhost:$HGPORT/
47 pulling from http://foo:***@localhost:$HGPORT/
48 searching for changes
48 searching for changes
49 no changes found
49 no changes found
50
50
51 $ hg rollback --dry-run --verbose
51 $ hg rollback --dry-run --verbose
52 repository tip rolled back to revision -1 (undo pull: http://foo:***@localhost:$HGPORT/)
52 repository tip rolled back to revision -1 (undo pull: http://foo:***@localhost:$HGPORT/)
53
53
54 Issue622: hg init && hg pull -u URL doesn't checkout default branch
54 Issue622: hg init && hg pull -u URL doesn't checkout default branch
55
55
56 $ cd ..
56 $ cd ..
57 $ hg init empty
57 $ hg init empty
58 $ cd empty
58 $ cd empty
59 $ hg pull -u ../test
59 $ hg pull -u ../test
60 pulling from ../test
60 pulling from ../test
61 requesting all changes
61 requesting all changes
62 adding changesets
62 adding changesets
63 adding manifests
63 adding manifests
64 adding file changes
64 adding file changes
65 added 1 changesets with 1 changes to 1 files
65 added 1 changesets with 1 changes to 1 files
66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67
67
68 Test 'file:' uri handling:
68 Test 'file:' uri handling:
69
69
70 $ hg pull -q file://../test-doesnt-exist
70 $ hg pull -q file://../test-doesnt-exist
71 abort: repository /test-doesnt-exist not found!
71 abort: repository /test-doesnt-exist not found!
72 [255]
72 [255]
73
73
74 $ hg pull -q file:../test
74 $ hg pull -q file:../test
75
75
76 It's tricky to make file:// URLs working on every platform with
76 It's tricky to make file:// URLs working on every platform with
77 regular shell commands.
77 regular shell commands.
78
78
79 $ URL=`python -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
79 $ URL=`python -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
80 $ hg pull -q "$URL"
80 $ hg pull -q "$URL"
81 abort: file:// URLs can only refer to localhost
82 [255]
81
83
84 $ URL=`python -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
85 $ hg pull -q "$URL"
@@ -1,192 +1,199 b''
1 import sys
1 import sys
2
2
3 def check(a, b):
3 def check(a, b):
4 if a != b:
4 if a != b:
5 print (a, b)
5 print (a, b)
6
6
7 def cert(cn):
7 def cert(cn):
8 return dict(subject=((('commonName', cn),),))
8 return dict(subject=((('commonName', cn),),))
9
9
10 from mercurial.url import _verifycert
10 from mercurial.url import _verifycert
11
11
12 # Test non-wildcard certificates
12 # Test non-wildcard certificates
13 check(_verifycert(cert('example.com'), 'example.com'),
13 check(_verifycert(cert('example.com'), 'example.com'),
14 None)
14 None)
15 check(_verifycert(cert('example.com'), 'www.example.com'),
15 check(_verifycert(cert('example.com'), 'www.example.com'),
16 'certificate is for example.com')
16 'certificate is for example.com')
17 check(_verifycert(cert('www.example.com'), 'example.com'),
17 check(_verifycert(cert('www.example.com'), 'example.com'),
18 'certificate is for www.example.com')
18 'certificate is for www.example.com')
19
19
20 # Test wildcard certificates
20 # Test wildcard certificates
21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
21 check(_verifycert(cert('*.example.com'), 'www.example.com'),
22 None)
22 None)
23 check(_verifycert(cert('*.example.com'), 'example.com'),
23 check(_verifycert(cert('*.example.com'), 'example.com'),
24 'certificate is for *.example.com')
24 'certificate is for *.example.com')
25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
25 check(_verifycert(cert('*.example.com'), 'w.w.example.com'),
26 'certificate is for *.example.com')
26 'certificate is for *.example.com')
27
27
28 # Test subjectAltName
28 # Test subjectAltName
29 san_cert = {'subject': ((('commonName', 'example.com'),),),
29 san_cert = {'subject': ((('commonName', 'example.com'),),),
30 'subjectAltName': (('DNS', '*.example.net'),
30 'subjectAltName': (('DNS', '*.example.net'),
31 ('DNS', 'example.net'))}
31 ('DNS', 'example.net'))}
32 check(_verifycert(san_cert, 'example.net'),
32 check(_verifycert(san_cert, 'example.net'),
33 None)
33 None)
34 check(_verifycert(san_cert, 'foo.example.net'),
34 check(_verifycert(san_cert, 'foo.example.net'),
35 None)
35 None)
36 # subject is only checked when subjectAltName is empty
36 # subject is only checked when subjectAltName is empty
37 check(_verifycert(san_cert, 'example.com'),
37 check(_verifycert(san_cert, 'example.com'),
38 'certificate is for *.example.net, example.net')
38 'certificate is for *.example.net, example.net')
39
39
40 # Avoid some pitfalls
40 # Avoid some pitfalls
41 check(_verifycert(cert('*.foo'), 'foo'),
41 check(_verifycert(cert('*.foo'), 'foo'),
42 'certificate is for *.foo')
42 'certificate is for *.foo')
43 check(_verifycert(cert('*o'), 'foo'),
43 check(_verifycert(cert('*o'), 'foo'),
44 'certificate is for *o')
44 'certificate is for *o')
45
45
46 check(_verifycert({'subject': ()},
46 check(_verifycert({'subject': ()},
47 'example.com'),
47 'example.com'),
48 'no commonName or subjectAltName found in certificate')
48 'no commonName or subjectAltName found in certificate')
49 check(_verifycert(None, 'example.com'),
49 check(_verifycert(None, 'example.com'),
50 'no certificate received')
50 'no certificate received')
51
51
52 import doctest
52 import doctest
53
53
54 def test_url():
54 def test_url():
55 """
55 """
56 >>> from mercurial.url import url
56 >>> from mercurial.url import url
57
57
58 This tests for edge cases in url.URL's parsing algorithm. Most of
58 This tests for edge cases in url.URL's parsing algorithm. Most of
59 these aren't useful for documentation purposes, so they aren't
59 these aren't useful for documentation purposes, so they aren't
60 part of the class's doc tests.
60 part of the class's doc tests.
61
61
62 Query strings and fragments:
62 Query strings and fragments:
63
63
64 >>> url('http://host/a?b#c')
64 >>> url('http://host/a?b#c')
65 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
65 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
66 >>> url('http://host/a?')
66 >>> url('http://host/a?')
67 <url scheme: 'http', host: 'host', path: 'a'>
67 <url scheme: 'http', host: 'host', path: 'a'>
68 >>> url('http://host/a#b#c')
68 >>> url('http://host/a#b#c')
69 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
69 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b#c'>
70 >>> url('http://host/a#b?c')
70 >>> url('http://host/a#b?c')
71 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
71 <url scheme: 'http', host: 'host', path: 'a', fragment: 'b?c'>
72 >>> url('http://host/?a#b')
72 >>> url('http://host/?a#b')
73 <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
73 <url scheme: 'http', host: 'host', path: '', query: 'a', fragment: 'b'>
74 >>> url('http://host/?a#b', parse_query=False)
74 >>> url('http://host/?a#b', parse_query=False)
75 <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
75 <url scheme: 'http', host: 'host', path: '?a', fragment: 'b'>
76 >>> url('http://host/?a#b', parse_fragment=False)
76 >>> url('http://host/?a#b', parse_fragment=False)
77 <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
77 <url scheme: 'http', host: 'host', path: '', query: 'a#b'>
78 >>> url('http://host/?a#b', parse_query=False, parse_fragment=False)
78 >>> url('http://host/?a#b', parse_query=False, parse_fragment=False)
79 <url scheme: 'http', host: 'host', path: '?a#b'>
79 <url scheme: 'http', host: 'host', path: '?a#b'>
80
80
81 IPv6 addresses:
81 IPv6 addresses:
82
82
83 >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
83 >>> url('ldap://[2001:db8::7]/c=GB?objectClass?one')
84 <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
84 <url scheme: 'ldap', host: '[2001:db8::7]', path: 'c=GB',
85 query: 'objectClass?one'>
85 query: 'objectClass?one'>
86 >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
86 >>> url('ldap://joe:xxx@[2001:db8::7]:80/c=GB?objectClass?one')
87 <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
87 <url scheme: 'ldap', user: 'joe', passwd: 'xxx', host: '[2001:db8::7]',
88 port: '80', path: 'c=GB', query: 'objectClass?one'>
88 port: '80', path: 'c=GB', query: 'objectClass?one'>
89
89
90 Missing scheme, host, etc.:
90 Missing scheme, host, etc.:
91
91
92 >>> url('://192.0.2.16:80/')
92 >>> url('://192.0.2.16:80/')
93 <url path: '://192.0.2.16:80/'>
93 <url path: '://192.0.2.16:80/'>
94 >>> url('http://mercurial.selenic.com')
94 >>> url('http://mercurial.selenic.com')
95 <url scheme: 'http', host: 'mercurial.selenic.com'>
95 <url scheme: 'http', host: 'mercurial.selenic.com'>
96 >>> url('/foo')
96 >>> url('/foo')
97 <url path: '/foo'>
97 <url path: '/foo'>
98 >>> url('bundle:/foo')
98 >>> url('bundle:/foo')
99 <url scheme: 'bundle', path: '/foo'>
99 <url scheme: 'bundle', path: '/foo'>
100 >>> url('a?b#c')
100 >>> url('a?b#c')
101 <url path: 'a?b', fragment: 'c'>
101 <url path: 'a?b', fragment: 'c'>
102 >>> url('http://x.com?arg=/foo')
102 >>> url('http://x.com?arg=/foo')
103 <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
103 <url scheme: 'http', host: 'x.com', query: 'arg=/foo'>
104 >>> url('http://joe:xxx@/foo')
104 >>> url('http://joe:xxx@/foo')
105 <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
105 <url scheme: 'http', user: 'joe', passwd: 'xxx', path: 'foo'>
106
106
107 Just a scheme and a path:
107 Just a scheme and a path:
108
108
109 >>> url('mailto:John.Doe@example.com')
109 >>> url('mailto:John.Doe@example.com')
110 <url scheme: 'mailto', path: 'John.Doe@example.com'>
110 <url scheme: 'mailto', path: 'John.Doe@example.com'>
111 >>> url('a:b:c:d')
111 >>> url('a:b:c:d')
112 <url path: 'a:b:c:d'>
112 <url path: 'a:b:c:d'>
113 >>> url('aa:bb:cc:dd')
113 >>> url('aa:bb:cc:dd')
114 <url scheme: 'aa', path: 'bb:cc:dd'>
114 <url scheme: 'aa', path: 'bb:cc:dd'>
115
115
116 SSH examples:
116 SSH examples:
117
117
118 >>> url('ssh://joe@host//home/joe')
118 >>> url('ssh://joe@host//home/joe')
119 <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
119 <url scheme: 'ssh', user: 'joe', host: 'host', path: '/home/joe'>
120 >>> url('ssh://joe:xxx@host/src')
120 >>> url('ssh://joe:xxx@host/src')
121 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
121 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', path: 'src'>
122 >>> url('ssh://joe:xxx@host')
122 >>> url('ssh://joe:xxx@host')
123 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
123 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host'>
124 >>> url('ssh://joe@host')
124 >>> url('ssh://joe@host')
125 <url scheme: 'ssh', user: 'joe', host: 'host'>
125 <url scheme: 'ssh', user: 'joe', host: 'host'>
126 >>> url('ssh://host')
126 >>> url('ssh://host')
127 <url scheme: 'ssh', host: 'host'>
127 <url scheme: 'ssh', host: 'host'>
128 >>> url('ssh://')
128 >>> url('ssh://')
129 <url scheme: 'ssh'>
129 <url scheme: 'ssh'>
130 >>> url('ssh:')
130 >>> url('ssh:')
131 <url scheme: 'ssh'>
131 <url scheme: 'ssh'>
132
132
133 Non-numeric port:
133 Non-numeric port:
134
134
135 >>> url('http://example.com:dd')
135 >>> url('http://example.com:dd')
136 <url scheme: 'http', host: 'example.com', port: 'dd'>
136 <url scheme: 'http', host: 'example.com', port: 'dd'>
137 >>> url('ssh://joe:xxx@host:ssh/foo')
137 >>> url('ssh://joe:xxx@host:ssh/foo')
138 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
138 <url scheme: 'ssh', user: 'joe', passwd: 'xxx', host: 'host', port: 'ssh',
139 path: 'foo'>
139 path: 'foo'>
140
140
141 Bad authentication credentials:
141 Bad authentication credentials:
142
142
143 >>> url('http://joe@joeville:123@4:@host/a?b#c')
143 >>> url('http://joe@joeville:123@4:@host/a?b#c')
144 <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
144 <url scheme: 'http', user: 'joe@joeville', passwd: '123@4:',
145 host: 'host', path: 'a', query: 'b', fragment: 'c'>
145 host: 'host', path: 'a', query: 'b', fragment: 'c'>
146 >>> url('http://!*#?/@!*#?/:@host/a?b#c')
146 >>> url('http://!*#?/@!*#?/:@host/a?b#c')
147 <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
147 <url scheme: 'http', host: '!*', fragment: '?/@!*#?/:@host/a?b#c'>
148 >>> url('http://!*#?@!*#?:@host/a?b#c')
148 >>> url('http://!*#?@!*#?:@host/a?b#c')
149 <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
149 <url scheme: 'http', host: '!*', fragment: '?@!*#?:@host/a?b#c'>
150 >>> url('http://!*@:!*@@host/a?b#c')
150 >>> url('http://!*@:!*@@host/a?b#c')
151 <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
151 <url scheme: 'http', user: '!*@', passwd: '!*@', host: 'host',
152 path: 'a', query: 'b', fragment: 'c'>
152 path: 'a', query: 'b', fragment: 'c'>
153
153
154 File paths:
154 File paths:
155
155
156 >>> url('a/b/c/d.g.f')
156 >>> url('a/b/c/d.g.f')
157 <url path: 'a/b/c/d.g.f'>
157 <url path: 'a/b/c/d.g.f'>
158 >>> url('/x///z/y/')
158 >>> url('/x///z/y/')
159 <url path: '/x///z/y/'>
159 <url path: '/x///z/y/'>
160
160
161 Non-localhost file URL:
162
163 >>> u = url('file://mercurial.selenic.com/foo')
164 Traceback (most recent call last):
165 File "<stdin>", line 1, in ?
166 Abort: file:// URLs can only refer to localhost
167
161 Empty URL:
168 Empty URL:
162
169
163 >>> u = url('')
170 >>> u = url('')
164 >>> u
171 >>> u
165 <url path: ''>
172 <url path: ''>
166 >>> str(u)
173 >>> str(u)
167 ''
174 ''
168
175
169 Empty path with query string:
176 Empty path with query string:
170
177
171 >>> str(url('http://foo/?bar'))
178 >>> str(url('http://foo/?bar'))
172 'http://foo/?bar'
179 'http://foo/?bar'
173
180
174 Invalid path:
181 Invalid path:
175
182
176 >>> u = url('http://foo/bar')
183 >>> u = url('http://foo/bar')
177 >>> u.path = 'bar'
184 >>> u.path = 'bar'
178 >>> str(u)
185 >>> str(u)
179 'http://foo/bar'
186 'http://foo/bar'
180
187
181 >>> u = url('file:///foo/bar/baz')
188 >>> u = url('file:///foo/bar/baz')
182 >>> u
189 >>> u
183 <url scheme: 'file', path: '/foo/bar/baz'>
190 <url scheme: 'file', path: '/foo/bar/baz'>
184 >>> str(u)
191 >>> str(u)
185 'file:/foo/bar/baz'
192 'file:/foo/bar/baz'
186 """
193 """
187
194
188 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
195 doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)
189
196
190 # Unicode (IDN) certname isn't supported
197 # Unicode (IDN) certname isn't supported
191 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
198 check(_verifycert(cert(u'\u4f8b.jp'), 'example.jp'),
192 'IDN in certificate not supported')
199 'IDN in certificate not supported')
General Comments 0
You need to be logged in to leave comments. Login now