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