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