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