##// END OF EJS Templates
url: fix https client authentication through proxy...
Mads Kiilerich -
r12906:ae163a0a stable
parent child Browse files
Show More
@@ -1,698 +1,698 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 _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 158 group, setting = key.split('.', 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, *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._data = __builtin__.open(*args, **kwargs)
266 266 self.read = self._data.read
267 267 self.seek = self._data.seek
268 268 self.close = self._data.close
269 269 self.write = self._data.write
270 270
271 271 def __len__(self):
272 272 return os.fstat(self._data.fileno()).st_size
273 273
274 274 def _gen_sendfile(connection):
275 275 def _sendfile(self, data):
276 276 # send a file
277 277 if isinstance(data, httpsendfile):
278 278 # if auth required, some data sent twice, so rewind here
279 279 data.seek(0)
280 280 for chunk in util.filechunkiter(data):
281 281 connection.send(self, chunk)
282 282 else:
283 283 connection.send(self, data)
284 284 return _sendfile
285 285
286 286 has_https = hasattr(urllib2, 'HTTPSHandler')
287 287 if has_https:
288 288 try:
289 289 # avoid using deprecated/broken FakeSocket in python 2.6
290 290 import ssl
291 291 _ssl_wrap_socket = ssl.wrap_socket
292 292 CERT_REQUIRED = ssl.CERT_REQUIRED
293 293 except ImportError:
294 294 CERT_REQUIRED = 2
295 295
296 296 def _ssl_wrap_socket(sock, key_file, cert_file,
297 297 cert_reqs=CERT_REQUIRED, ca_certs=None):
298 298 if ca_certs:
299 299 raise util.Abort(_(
300 300 'certificate checking requires Python 2.6'))
301 301
302 302 ssl = socket.ssl(sock, key_file, cert_file)
303 303 return httplib.FakeSocket(sock, ssl)
304 304
305 305 try:
306 306 _create_connection = socket.create_connection
307 307 except AttributeError:
308 308 _GLOBAL_DEFAULT_TIMEOUT = object()
309 309
310 310 def _create_connection(address, timeout=_GLOBAL_DEFAULT_TIMEOUT,
311 311 source_address=None):
312 312 # lifted from Python 2.6
313 313
314 314 msg = "getaddrinfo returns an empty list"
315 315 host, port = address
316 316 for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
317 317 af, socktype, proto, canonname, sa = res
318 318 sock = None
319 319 try:
320 320 sock = socket.socket(af, socktype, proto)
321 321 if timeout is not _GLOBAL_DEFAULT_TIMEOUT:
322 322 sock.settimeout(timeout)
323 323 if source_address:
324 324 sock.bind(source_address)
325 325 sock.connect(sa)
326 326 return sock
327 327
328 328 except socket.error, msg:
329 329 if sock is not None:
330 330 sock.close()
331 331
332 332 raise socket.error, msg
333 333
334 334 class httpconnection(keepalive.HTTPConnection):
335 335 # must be able to send big bundle as stream.
336 336 send = _gen_sendfile(keepalive.HTTPConnection)
337 337
338 338 def connect(self):
339 339 if has_https and self.realhostport: # use CONNECT proxy
340 340 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
341 341 self.sock.connect((self.host, self.port))
342 342 if _generic_proxytunnel(self):
343 343 # we do not support client x509 certificates
344 344 self.sock = _ssl_wrap_socket(self.sock, None, None)
345 345 else:
346 346 keepalive.HTTPConnection.connect(self)
347 347
348 348 def getresponse(self):
349 349 proxyres = getattr(self, 'proxyres', None)
350 350 if proxyres:
351 351 if proxyres.will_close:
352 352 self.close()
353 353 self.proxyres = None
354 354 return proxyres
355 355 return keepalive.HTTPConnection.getresponse(self)
356 356
357 357 # general transaction handler to support different ways to handle
358 358 # HTTPS proxying before and after Python 2.6.3.
359 359 def _generic_start_transaction(handler, h, req):
360 360 if hasattr(req, '_tunnel_host') and req._tunnel_host:
361 361 tunnel_host = req._tunnel_host
362 362 if tunnel_host[:7] not in ['http://', 'https:/']:
363 363 tunnel_host = 'https://' + tunnel_host
364 364 new_tunnel = True
365 365 else:
366 366 tunnel_host = req.get_selector()
367 367 new_tunnel = False
368 368
369 369 if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
370 370 urlparts = urlparse.urlparse(tunnel_host)
371 371 if new_tunnel or urlparts[0] == 'https': # only use CONNECT for HTTPS
372 372 realhostport = urlparts[1]
373 373 if realhostport[-1] == ']' or ':' not in realhostport:
374 374 realhostport += ':443'
375 375
376 376 h.realhostport = realhostport
377 377 h.headers = req.headers.copy()
378 378 h.headers.update(handler.parent.addheaders)
379 379 return
380 380
381 381 h.realhostport = None
382 382 h.headers = None
383 383
384 384 def _generic_proxytunnel(self):
385 385 proxyheaders = dict(
386 386 [(x, self.headers[x]) for x in self.headers
387 387 if x.lower().startswith('proxy-')])
388 388 self._set_hostport(self.host, self.port)
389 389 self.send('CONNECT %s HTTP/1.0\r\n' % self.realhostport)
390 390 for header in proxyheaders.iteritems():
391 391 self.send('%s: %s\r\n' % header)
392 392 self.send('\r\n')
393 393
394 394 # majority of the following code is duplicated from
395 395 # httplib.HTTPConnection as there are no adequate places to
396 396 # override functions to provide the needed functionality
397 397 res = self.response_class(self.sock,
398 398 strict=self.strict,
399 399 method=self._method)
400 400
401 401 while True:
402 402 version, status, reason = res._read_status()
403 403 if status != httplib.CONTINUE:
404 404 break
405 405 while True:
406 406 skip = res.fp.readline().strip()
407 407 if not skip:
408 408 break
409 409 res.status = status
410 410 res.reason = reason.strip()
411 411
412 412 if res.status == 200:
413 413 while True:
414 414 line = res.fp.readline()
415 415 if line == '\r\n':
416 416 break
417 417 return True
418 418
419 419 if version == 'HTTP/1.0':
420 420 res.version = 10
421 421 elif version.startswith('HTTP/1.'):
422 422 res.version = 11
423 423 elif version == 'HTTP/0.9':
424 424 res.version = 9
425 425 else:
426 426 raise httplib.UnknownProtocol(version)
427 427
428 428 if res.version == 9:
429 429 res.length = None
430 430 res.chunked = 0
431 431 res.will_close = 1
432 432 res.msg = httplib.HTTPMessage(cStringIO.StringIO())
433 433 return False
434 434
435 435 res.msg = httplib.HTTPMessage(res.fp)
436 436 res.msg.fp = None
437 437
438 438 # are we using the chunked-style of transfer encoding?
439 439 trenc = res.msg.getheader('transfer-encoding')
440 440 if trenc and trenc.lower() == "chunked":
441 441 res.chunked = 1
442 442 res.chunk_left = None
443 443 else:
444 444 res.chunked = 0
445 445
446 446 # will the connection close at the end of the response?
447 447 res.will_close = res._check_close()
448 448
449 449 # do we have a Content-Length?
450 450 # NOTE: RFC 2616, S4.4, #3 says we ignore this if tr_enc is "chunked"
451 451 length = res.msg.getheader('content-length')
452 452 if length and not res.chunked:
453 453 try:
454 454 res.length = int(length)
455 455 except ValueError:
456 456 res.length = None
457 457 else:
458 458 if res.length < 0: # ignore nonsensical negative lengths
459 459 res.length = None
460 460 else:
461 461 res.length = None
462 462
463 463 # does the body have a fixed length? (of zero)
464 464 if (status == httplib.NO_CONTENT or status == httplib.NOT_MODIFIED or
465 465 100 <= status < 200 or # 1xx codes
466 466 res._method == 'HEAD'):
467 467 res.length = 0
468 468
469 469 # if the connection remains open, and we aren't using chunked, and
470 470 # a content-length was not provided, then assume that the connection
471 471 # WILL close.
472 472 if (not res.will_close and
473 473 not res.chunked and
474 474 res.length is None):
475 475 res.will_close = 1
476 476
477 477 self.proxyres = res
478 478
479 479 return False
480 480
481 481 class httphandler(keepalive.HTTPHandler):
482 482 def http_open(self, req):
483 483 return self.do_open(httpconnection, req)
484 484
485 485 def _start_transaction(self, h, req):
486 486 _generic_start_transaction(self, h, req)
487 487 return keepalive.HTTPHandler._start_transaction(self, h, req)
488 488
489 489 def _verifycert(cert, hostname):
490 490 '''Verify that cert (in socket.getpeercert() format) matches hostname.
491 491 CRLs and subjectAltName are not handled.
492 492
493 493 Returns error message if any problems are found and None on success.
494 494 '''
495 495 if not cert:
496 496 return _('no certificate received')
497 497 dnsname = hostname.lower()
498 498 for s in cert.get('subject', []):
499 499 key, value = s[0]
500 500 if key == 'commonName':
501 501 certname = value.lower()
502 502 if (certname == dnsname or
503 503 '.' in dnsname and certname == '*.' + dnsname.split('.', 1)[1]):
504 504 return None
505 505 return _('certificate is for %s') % certname
506 506 return _('no commonName found in certificate')
507 507
508 508 if has_https:
509 509 class BetterHTTPS(httplib.HTTPSConnection):
510 510 send = keepalive.safesend
511 511
512 512 def connect(self):
513 513 if hasattr(self, 'ui'):
514 514 cacerts = self.ui.config('web', 'cacerts')
515 515 else:
516 516 cacerts = None
517 517
518 518 if cacerts:
519 519 sock = _create_connection((self.host, self.port))
520 520 self.sock = _ssl_wrap_socket(sock, self.key_file,
521 521 self.cert_file, cert_reqs=CERT_REQUIRED,
522 522 ca_certs=cacerts)
523 523 msg = _verifycert(self.sock.getpeercert(), self.host)
524 524 if msg:
525 525 raise util.Abort(_('%s certificate error: %s') %
526 526 (self.host, msg))
527 527 self.ui.debug('%s certificate successfully verified\n' %
528 528 self.host)
529 529 else:
530 530 httplib.HTTPSConnection.connect(self)
531 531
532 532 class httpsconnection(BetterHTTPS):
533 533 response_class = keepalive.HTTPResponse
534 534 # must be able to send big bundle as stream.
535 535 send = _gen_sendfile(BetterHTTPS)
536 536 getresponse = keepalive.wrapgetresponse(httplib.HTTPSConnection)
537 537
538 538 def connect(self):
539 539 if self.realhostport: # use CONNECT proxy
540 540 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
541 541 self.sock.connect((self.host, self.port))
542 542 if _generic_proxytunnel(self):
543 self.sock = _ssl_wrap_socket(self.sock, self.cert_file,
544 self.key_file)
543 self.sock = _ssl_wrap_socket(self.sock, self.key_file,
544 self.cert_file)
545 545 else:
546 546 BetterHTTPS.connect(self)
547 547
548 548 class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
549 549 def __init__(self, ui):
550 550 keepalive.KeepAliveHandler.__init__(self)
551 551 urllib2.HTTPSHandler.__init__(self)
552 552 self.ui = ui
553 553 self.pwmgr = passwordmgr(self.ui)
554 554
555 555 def _start_transaction(self, h, req):
556 556 _generic_start_transaction(self, h, req)
557 557 return keepalive.KeepAliveHandler._start_transaction(self, h, req)
558 558
559 559 def https_open(self, req):
560 560 self.auth = self.pwmgr.readauthtoken(req.get_full_url())
561 561 return self.do_open(self._makeconnection, req)
562 562
563 563 def _makeconnection(self, host, port=None, *args, **kwargs):
564 564 keyfile = None
565 565 certfile = None
566 566
567 567 if len(args) >= 1: # key_file
568 568 keyfile = args[0]
569 569 if len(args) >= 2: # cert_file
570 570 certfile = args[1]
571 571 args = args[2:]
572 572
573 573 # if the user has specified different key/cert files in
574 574 # hgrc, we prefer these
575 575 if self.auth and 'key' in self.auth and 'cert' in self.auth:
576 576 keyfile = self.auth['key']
577 577 certfile = self.auth['cert']
578 578
579 579 conn = httpsconnection(host, port, keyfile, certfile, *args, **kwargs)
580 580 conn.ui = self.ui
581 581 return conn
582 582
583 583 class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
584 584 def __init__(self, *args, **kwargs):
585 585 urllib2.HTTPDigestAuthHandler.__init__(self, *args, **kwargs)
586 586 self.retried_req = None
587 587
588 588 def reset_retry_count(self):
589 589 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
590 590 # forever. We disable reset_retry_count completely and reset in
591 591 # http_error_auth_reqed instead.
592 592 pass
593 593
594 594 def http_error_auth_reqed(self, auth_header, host, req, headers):
595 595 # Reset the retry counter once for each request.
596 596 if req is not self.retried_req:
597 597 self.retried_req = req
598 598 self.retried = 0
599 599 # In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
600 600 # it doesn't know about the auth type requested. This can happen if
601 601 # somebody is using BasicAuth and types a bad password.
602 602 try:
603 603 return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
604 604 self, auth_header, host, req, headers)
605 605 except ValueError, inst:
606 606 arg = inst.args[0]
607 607 if arg.startswith("AbstractDigestAuthHandler doesn't know "):
608 608 return
609 609 raise
610 610
611 611 class httpbasicauthhandler(urllib2.HTTPBasicAuthHandler):
612 612 def __init__(self, *args, **kwargs):
613 613 urllib2.HTTPBasicAuthHandler.__init__(self, *args, **kwargs)
614 614 self.retried_req = None
615 615
616 616 def reset_retry_count(self):
617 617 # Python 2.6.5 will call this on 401 or 407 errors and thus loop
618 618 # forever. We disable reset_retry_count completely and reset in
619 619 # http_error_auth_reqed instead.
620 620 pass
621 621
622 622 def http_error_auth_reqed(self, auth_header, host, req, headers):
623 623 # Reset the retry counter once for each request.
624 624 if req is not self.retried_req:
625 625 self.retried_req = req
626 626 self.retried = 0
627 627 return urllib2.HTTPBasicAuthHandler.http_error_auth_reqed(
628 628 self, auth_header, host, req, headers)
629 629
630 630 def getauthinfo(path):
631 631 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
632 632 if not urlpath:
633 633 urlpath = '/'
634 634 if scheme != 'file':
635 635 # XXX: why are we quoting the path again with some smart
636 636 # heuristic here? Anyway, it cannot be done with file://
637 637 # urls since path encoding is os/fs dependent (see
638 638 # urllib.pathname2url() for details).
639 639 urlpath = quotepath(urlpath)
640 640 host, port, user, passwd = netlocsplit(netloc)
641 641
642 642 # urllib cannot handle URLs with embedded user or passwd
643 643 url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
644 644 urlpath, query, frag))
645 645 if user:
646 646 netloc = host
647 647 if port:
648 648 netloc += ':' + port
649 649 # Python < 2.4.3 uses only the netloc to search for a password
650 650 authinfo = (None, (url, netloc), user, passwd or '')
651 651 else:
652 652 authinfo = None
653 653 return url, authinfo
654 654
655 655 handlerfuncs = []
656 656
657 657 def opener(ui, authinfo=None):
658 658 '''
659 659 construct an opener suitable for urllib2
660 660 authinfo will be added to the password manager
661 661 '''
662 662 handlers = [httphandler()]
663 663 if has_https:
664 664 handlers.append(httpshandler(ui))
665 665
666 666 handlers.append(proxyhandler(ui))
667 667
668 668 passmgr = passwordmgr(ui)
669 669 if authinfo is not None:
670 670 passmgr.add_password(*authinfo)
671 671 user, passwd = authinfo[2:4]
672 672 ui.debug('http auth: user %s, password %s\n' %
673 673 (user, passwd and '*' * len(passwd) or 'not set'))
674 674
675 675 handlers.extend((httpbasicauthhandler(passmgr),
676 676 httpdigestauthhandler(passmgr)))
677 677 handlers.extend([h(ui, passmgr) for h in handlerfuncs])
678 678 opener = urllib2.build_opener(*handlers)
679 679
680 680 # 1.0 here is the _protocol_ version
681 681 opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
682 682 opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
683 683 return opener
684 684
685 685 scheme_re = re.compile(r'^([a-zA-Z0-9+-.]+)://')
686 686
687 687 def open(ui, url, data=None):
688 688 scheme = None
689 689 m = scheme_re.search(url)
690 690 if m:
691 691 scheme = m.group(1).lower()
692 692 if not scheme:
693 693 path = util.normpath(os.path.abspath(url))
694 694 url = 'file://' + urllib.pathname2url(path)
695 695 authinfo = None
696 696 else:
697 697 url, authinfo = getauthinfo(url)
698 698 return opener(ui, authinfo).open(url, data)
General Comments 0
You need to be logged in to leave comments. Login now