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