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