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