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