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