changeset 7270:2db33c1a5654

factor out the url handling from httprepo Create url.py to handle all the url handling: - proxy handling - workaround various python bugs - handle username/password embedded in the url
author Benoit Boissinot <benoit.boissinot@ens-lyon.org>
date Mon, 27 Oct 2008 21:50:01 +0100
parents 95a53961d7a6
children 8046f0a070a6
files hgext/fetch.py mercurial/commands.py mercurial/httprepo.py mercurial/url.py mercurial/util.py
diffstat 5 files changed, 319 insertions(+), 275 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/fetch.py	Mon Oct 27 17:48:05 2008 +0100
+++ b/hgext/fetch.py	Mon Oct 27 21:50:01 2008 +0100
@@ -8,7 +8,7 @@
 
 from mercurial.i18n import _
 from mercurial.node import nullid, short
-from mercurial import commands, cmdutil, hg, util
+from mercurial import commands, cmdutil, hg, util, url
 
 def fetch(ui, repo, source='default', **opts):
     '''Pull changes from a remote repository, merge new changes if needed.
@@ -60,7 +60,7 @@
 
         other = hg.repository(ui, ui.expandpath(source))
         ui.status(_('pulling from %s\n') %
-                  util.hidepassword(ui.expandpath(source)))
+                  url.hidepassword(ui.expandpath(source)))
         revs = None
         if opts['rev']:
             if not other.local():
@@ -118,7 +118,7 @@
             mod, add, rem = repo.status()[:3]
             message = (cmdutil.logmessage(opts) or
                        (_('Automated merge with %s') %
-                        util.removeauth(other.url())))
+                        url.removeauth(other.url())))
             force_editor = opts.get('force_editor') or opts.get('edit')
             n = repo.commit(mod + add + rem, message,
                             opts['user'], opts['date'], force=True,
--- a/mercurial/commands.py	Mon Oct 27 17:48:05 2008 +0100
+++ b/mercurial/commands.py	Mon Oct 27 21:50:01 2008 +0100
@@ -10,7 +10,7 @@
 from i18n import _, gettext
 import os, re, sys, urllib
 import hg, util, revlog, bundlerepo, extensions, copies
-import difflib, patch, time, help, mdiff, tempfile
+import difflib, patch, time, help, mdiff, tempfile, url
 import version, socket
 import archival, changegroup, cmdutil, hgweb.server, sshserver, hbisect
 import merge as merge_
@@ -1675,7 +1675,7 @@
     cmdutil.setremoteconfig(ui, opts)
 
     other = hg.repository(ui, source)
-    ui.status(_('comparing with %s\n') % util.hidepassword(source))
+    ui.status(_('comparing with %s\n') % url.hidepassword(source))
     if revs:
         revs = [other.lookup(rev) for rev in revs]
     incoming = repo.findincoming(other, heads=revs, force=opts["force"])
@@ -1997,7 +1997,7 @@
         revs = [repo.lookup(rev) for rev in revs]
 
     other = hg.repository(ui, dest)
-    ui.status(_('comparing with %s\n') % util.hidepassword(dest))
+    ui.status(_('comparing with %s\n') % url.hidepassword(dest))
     o = repo.findoutgoing(other, force=opts.get('force'))
     if not o:
         ui.status(_("no changes found\n"))
@@ -2068,13 +2068,13 @@
     if search:
         for name, path in ui.configitems("paths"):
             if name == search:
-                ui.write("%s\n" % util.hidepassword(path))
+                ui.write("%s\n" % url.hidepassword(path))
                 return
         ui.warn(_("not found!\n"))
         return 1
     else:
         for name, path in ui.configitems("paths"):
-            ui.write("%s = %s\n" % (name, util.hidepassword(path)))
+            ui.write("%s = %s\n" % (name, url.hidepassword(path)))
 
 def postincoming(ui, repo, modheads, optupdate, checkout):
     if modheads == 0:
@@ -2131,7 +2131,7 @@
     cmdutil.setremoteconfig(ui, opts)
 
     other = hg.repository(ui, source)
-    ui.status(_('pulling from %s\n') % util.hidepassword(source))
+    ui.status(_('pulling from %s\n') % url.hidepassword(source))
     if revs:
         try:
             revs = [other.lookup(rev) for rev in revs]
@@ -2179,7 +2179,7 @@
     cmdutil.setremoteconfig(ui, opts)
 
     other = hg.repository(ui, dest)
-    ui.status(_('pushing to %s\n') % util.hidepassword(dest))
+    ui.status(_('pushing to %s\n') % url.hidepassword(dest))
     if revs:
         revs = [repo.lookup(rev) for rev in revs]
     r = repo.push(other, opts.get('force'), revs=revs)
--- a/mercurial/httprepo.py	Mon Oct 27 17:48:05 2008 +0100
+++ b/mercurial/httprepo.py	Mon Oct 27 21:50:01 2008 +0100
@@ -10,189 +10,7 @@
 from i18n import _
 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
 import errno, keepalive, socket, changegroup, statichttprepo
-
-class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
-    def __init__(self, ui):
-        urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
-        self.ui = ui
-
-    def find_user_password(self, realm, authuri):
-        authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
-            self, realm, authuri)
-        user, passwd = authinfo
-        if user and passwd:
-            return (user, passwd)
-
-        if not self.ui.interactive:
-            raise util.Abort(_('http authorization required'))
-
-        self.ui.write(_("http authorization required\n"))
-        self.ui.status(_("realm: %s\n") % realm)
-        if user:
-            self.ui.status(_("user: %s\n") % user)
-        else:
-            user = self.ui.prompt(_("user:"), default=None)
-
-        if not passwd:
-            passwd = self.ui.getpass()
-
-        self.add_password(realm, authuri, user, passwd)
-        return (user, passwd)
-
-class proxyhandler(urllib2.ProxyHandler):
-    def __init__(self, ui):
-        proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
-        # XXX proxyauthinfo = None
-
-        if proxyurl:
-            # proxy can be proper url or host[:port]
-            if not (proxyurl.startswith('http:') or
-                    proxyurl.startswith('https:')):
-                proxyurl = 'http://' + proxyurl + '/'
-            snpqf = urlparse.urlsplit(proxyurl)
-            proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
-            hpup = netlocsplit(proxynetloc)
-
-            proxyhost, proxyport, proxyuser, proxypasswd = hpup
-            if not proxyuser:
-                proxyuser = ui.config("http_proxy", "user")
-                proxypasswd = ui.config("http_proxy", "passwd")
-
-            # see if we should use a proxy for this url
-            no_list = [ "localhost", "127.0.0.1" ]
-            no_list.extend([p.lower() for
-                            p in ui.configlist("http_proxy", "no")])
-            no_list.extend([p.strip().lower() for
-                            p in os.getenv("no_proxy", '').split(',')
-                            if p.strip()])
-            # "http_proxy.always" config is for running tests on localhost
-            if ui.configbool("http_proxy", "always"):
-                self.no_list = []
-            else:
-                self.no_list = no_list
-
-            proxyurl = urlparse.urlunsplit((
-                proxyscheme, netlocunsplit(proxyhost, proxyport,
-                                           proxyuser, proxypasswd or ''),
-                proxypath, proxyquery, proxyfrag))
-            proxies = {'http': proxyurl, 'https': proxyurl}
-            ui.debug(_('proxying through http://%s:%s\n') %
-                      (proxyhost, proxyport))
-        else:
-            proxies = {}
-
-        # urllib2 takes proxy values from the environment and those
-        # will take precedence if found, so drop them
-        for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
-            try:
-                if env in os.environ:
-                    del os.environ[env]
-            except OSError:
-                pass
-
-        urllib2.ProxyHandler.__init__(self, proxies)
-        self.ui = ui
-
-    def proxy_open(self, req, proxy, type):
-        host = req.get_host().split(':')[0]
-        if host in self.no_list:
-            return None
-        return urllib2.ProxyHandler.proxy_open(self, req, proxy, type)
-
-def netlocsplit(netloc):
-    '''split [user[:passwd]@]host[:port] into 4-tuple.'''
-
-    a = netloc.find('@')
-    if a == -1:
-        user, passwd = None, None
-    else:
-        userpass, netloc = netloc[:a], netloc[a+1:]
-        c = userpass.find(':')
-        if c == -1:
-            user, passwd = urllib.unquote(userpass), None
-        else:
-            user = urllib.unquote(userpass[:c])
-            passwd = urllib.unquote(userpass[c+1:])
-    c = netloc.find(':')
-    if c == -1:
-        host, port = netloc, None
-    else:
-        host, port = netloc[:c], netloc[c+1:]
-    return host, port, user, passwd
-
-def netlocunsplit(host, port, user=None, passwd=None):
-    '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
-    if port:
-        hostport = host + ':' + port
-    else:
-        hostport = host
-    if user:
-        if passwd:
-            userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
-        else:
-            userpass = urllib.quote(user)
-        return userpass + '@' + hostport
-    return hostport
-
-# work around a bug in Python < 2.4.2
-# (it leaves a "\n" at the end of Proxy-authorization headers)
-class request(urllib2.Request):
-    def add_header(self, key, val):
-        if key.lower() == 'proxy-authorization':
-            val = val.strip()
-        return urllib2.Request.add_header(self, key, val)
-
-class httpsendfile(file):
-    def __len__(self):
-        return os.fstat(self.fileno()).st_size
-
-def _gen_sendfile(connection):
-    def _sendfile(self, data):
-        # send a file
-        if isinstance(data, httpsendfile):
-            # if auth required, some data sent twice, so rewind here
-            data.seek(0)
-            for chunk in util.filechunkiter(data):
-                connection.send(self, chunk)
-        else:
-            connection.send(self, data)
-    return _sendfile
-
-class httpconnection(keepalive.HTTPConnection):
-    # must be able to send big bundle as stream.
-    send = _gen_sendfile(keepalive.HTTPConnection)
-
-class httphandler(keepalive.HTTPHandler):
-    def http_open(self, req):
-        return self.do_open(httpconnection, req)
-
-    def __del__(self):
-        self.close_all()
-
-has_https = hasattr(urllib2, 'HTTPSHandler')
-if has_https:
-    class httpsconnection(httplib.HTTPSConnection):
-        response_class = keepalive.HTTPResponse
-        # must be able to send big bundle as stream.
-        send = _gen_sendfile(httplib.HTTPSConnection)
-
-    class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
-        def https_open(self, req):
-            return self.do_open(httpsconnection, req)
-
-# In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
-# it doesn't know about the auth type requested.  This can happen if
-# somebody is using BasicAuth and types a bad password.
-class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
-    def http_error_auth_reqed(self, auth_header, host, req, headers):
-        try:
-            return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
-                        self, auth_header, host, req, headers)
-        except ValueError, inst:
-            arg = inst.args[0]
-            if arg.startswith("AbstractDigestAuthHandler doesn't know "):
-                return
-            raise
+import url
 
 def zgenerator(f):
     zd = zlib.decompressobj()
@@ -203,43 +21,6 @@
         raise IOError(None, _('connection ended unexpectedly'))
     yield zd.flush()
 
-_safe = ('abcdefghijklmnopqrstuvwxyz'
-         'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
-         '0123456789' '_.-/')
-_safeset = None
-_hex = None
-def quotepath(path):
-    '''quote the path part of a URL
-
-    This is similar to urllib.quote, but it also tries to avoid
-    quoting things twice (inspired by wget):
-
-    >>> quotepath('abc def')
-    'abc%20def'
-    >>> quotepath('abc%20def')
-    'abc%20def'
-    >>> quotepath('abc%20 def')
-    'abc%20%20def'
-    >>> quotepath('abc def%20')
-    'abc%20def%20'
-    >>> quotepath('abc def%2')
-    'abc%20def%252'
-    >>> quotepath('abc def%')
-    'abc%20def%25'
-    '''
-    global _safeset, _hex
-    if _safeset is None:
-        _safeset = util.set(_safe)
-        _hex = util.set('abcdefABCDEF0123456789')
-    l = list(path)
-    for i in xrange(len(l)):
-        c = l[i]
-        if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
-            pass
-        elif c not in _safeset:
-            l[i] = '%%%02X' % ord(c)
-    return ''.join(l)
-
 class httprepository(repo.repository):
     def __init__(self, ui, path):
         self.path = path
@@ -249,41 +30,14 @@
         if query or frag:
             raise util.Abort(_('unsupported URL component: "%s"') %
                              (query or frag))
-        if not urlpath:
-            urlpath = '/'
-        urlpath = quotepath(urlpath)
-        host, port, user, passwd = netlocsplit(netloc)
 
         # urllib cannot handle URLs with embedded user or passwd
-        self._url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
-                                         urlpath, '', ''))
+        self._url, authinfo = url.getauthinfo(path)
+
         self.ui = ui
         self.ui.debug(_('using %s\n') % self._url)
 
-        handlers = [httphandler()]
-        if has_https:
-            handlers.append(httpshandler())
-
-        handlers.append(proxyhandler(ui))
-
-        passmgr = passwordmgr(ui)
-        if user:
-            ui.debug(_('http auth: user %s, password %s\n') %
-                     (user, passwd and '*' * len(passwd) or 'not set'))
-            netloc = host
-            if port:
-                netloc += ':' + port
-            # Python < 2.4.3 uses only the netloc to search for a password
-            passmgr.add_password(None, (self._url, netloc), user, passwd or '')
-
-        handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
-                         httpdigestauthhandler(passmgr)))
-        opener = urllib2.build_opener(*handlers)
-
-        # 1.0 here is the _protocol_ version
-        opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
-        opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
-        urllib2.install_opener(opener)
+        self.urlopener = url.opener(ui, authinfo)
 
     def url(self):
         return self.path
@@ -316,7 +70,7 @@
         try:
             if data:
                 self.ui.debug(_("sending %s bytes\n") % len(data))
-            resp = urllib2.urlopen(request(cu, data, headers))
+            resp = self.urlopener.open(urllib2.Request(cu, data, headers))
         except urllib2.HTTPError, inst:
             if inst.code == 401:
                 raise util.Abort(_('authorization failed'))
@@ -433,7 +187,7 @@
                     break
 
         tempname = changegroup.writebundle(cg, None, type)
-        fp = httpsendfile(tempname, "rb")
+        fp = url.httpsendfile(tempname, "rb")
         try:
             try:
                 resp = self.do_read(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/url.py	Mon Oct 27 21:50:01 2008 +0100
@@ -0,0 +1,302 @@
+# url.py - HTTP handling for mercurial
+#
+# Copyright 2005, 2006, 2007, 2008 Matt Mackall <mpm@selenic.com>
+# Copyright 2006, 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br>
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import urllib, urllib2, urlparse, httplib, os, re
+from i18n import _
+import keepalive, util
+
+def hidepassword(url):
+    '''hide user credential in a url string'''
+    scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
+    netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
+    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
+
+def removeauth(url):
+    '''remove all authentication information from a url string'''
+    scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
+    netloc = netloc[netloc.find('@')+1:]
+    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
+
+def netlocsplit(netloc):
+    '''split [user[:passwd]@]host[:port] into 4-tuple.'''
+
+    a = netloc.find('@')
+    if a == -1:
+        user, passwd = None, None
+    else:
+        userpass, netloc = netloc[:a], netloc[a+1:]
+        c = userpass.find(':')
+        if c == -1:
+            user, passwd = urllib.unquote(userpass), None
+        else:
+            user = urllib.unquote(userpass[:c])
+            passwd = urllib.unquote(userpass[c+1:])
+    c = netloc.find(':')
+    if c == -1:
+        host, port = netloc, None
+    else:
+        host, port = netloc[:c], netloc[c+1:]
+    return host, port, user, passwd
+
+def netlocunsplit(host, port, user=None, passwd=None):
+    '''turn host, port, user, passwd into [user[:passwd]@]host[:port].'''
+    if port:
+        hostport = host + ':' + port
+    else:
+        hostport = host
+    if user:
+        if passwd:
+            userpass = urllib.quote(user) + ':' + urllib.quote(passwd)
+        else:
+            userpass = urllib.quote(user)
+        return userpass + '@' + hostport
+    return hostport
+
+_safe = ('abcdefghijklmnopqrstuvwxyz'
+         'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+         '0123456789' '_.-/')
+_safeset = None
+_hex = None
+def quotepath(path):
+    '''quote the path part of a URL
+
+    This is similar to urllib.quote, but it also tries to avoid
+    quoting things twice (inspired by wget):
+
+    >>> quotepath('abc def')
+    'abc%20def'
+    >>> quotepath('abc%20def')
+    'abc%20def'
+    >>> quotepath('abc%20 def')
+    'abc%20%20def'
+    >>> quotepath('abc def%20')
+    'abc%20def%20'
+    >>> quotepath('abc def%2')
+    'abc%20def%252'
+    >>> quotepath('abc def%')
+    'abc%20def%25'
+    '''
+    global _safeset, _hex
+    if _safeset is None:
+        _safeset = util.set(_safe)
+        _hex = util.set('abcdefABCDEF0123456789')
+    l = list(path)
+    for i in xrange(len(l)):
+        c = l[i]
+        if c == '%' and i + 2 < len(l) and (l[i+1] in _hex and l[i+2] in _hex):
+            pass
+        elif c not in _safeset:
+            l[i] = '%%%02X' % ord(c)
+    return ''.join(l)
+
+class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm):
+    def __init__(self, ui):
+        urllib2.HTTPPasswordMgrWithDefaultRealm.__init__(self)
+        self.ui = ui
+
+    def find_user_password(self, realm, authuri):
+        authinfo = urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password(
+            self, realm, authuri)
+        user, passwd = authinfo
+        if user and passwd:
+            return (user, passwd)
+
+        if not self.ui.interactive:
+            raise util.Abort(_('http authorization required'))
+
+        self.ui.write(_("http authorization required\n"))
+        self.ui.status(_("realm: %s\n") % realm)
+        if user:
+            self.ui.status(_("user: %s\n") % user)
+        else:
+            user = self.ui.prompt(_("user:"), default=None)
+
+        if not passwd:
+            passwd = self.ui.getpass()
+
+        self.add_password(realm, authuri, user, passwd)
+        return (user, passwd)
+
+class proxyhandler(urllib2.ProxyHandler):
+    def __init__(self, ui):
+        proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy')
+        # XXX proxyauthinfo = None
+
+        if proxyurl:
+            # proxy can be proper url or host[:port]
+            if not (proxyurl.startswith('http:') or
+                    proxyurl.startswith('https:')):
+                proxyurl = 'http://' + proxyurl + '/'
+            snpqf = urlparse.urlsplit(proxyurl)
+            proxyscheme, proxynetloc, proxypath, proxyquery, proxyfrag = snpqf
+            hpup = netlocsplit(proxynetloc)
+
+            proxyhost, proxyport, proxyuser, proxypasswd = hpup
+            if not proxyuser:
+                proxyuser = ui.config("http_proxy", "user")
+                proxypasswd = ui.config("http_proxy", "passwd")
+
+            # see if we should use a proxy for this url
+            no_list = [ "localhost", "127.0.0.1" ]
+            no_list.extend([p.lower() for
+                            p in ui.configlist("http_proxy", "no")])
+            no_list.extend([p.strip().lower() for
+                            p in os.getenv("no_proxy", '').split(',')
+                            if p.strip()])
+            # "http_proxy.always" config is for running tests on localhost
+            if ui.configbool("http_proxy", "always"):
+                self.no_list = []
+            else:
+                self.no_list = no_list
+
+            proxyurl = urlparse.urlunsplit((
+                proxyscheme, netlocunsplit(proxyhost, proxyport,
+                                                proxyuser, proxypasswd or ''),
+                proxypath, proxyquery, proxyfrag))
+            proxies = {'http': proxyurl, 'https': proxyurl}
+            ui.debug(_('proxying through http://%s:%s\n') %
+                      (proxyhost, proxyport))
+        else:
+            proxies = {}
+
+        # urllib2 takes proxy values from the environment and those
+        # will take precedence if found, so drop them
+        for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
+            try:
+                if env in os.environ:
+                    del os.environ[env]
+            except OSError:
+                pass
+
+        urllib2.ProxyHandler.__init__(self, proxies)
+        self.ui = ui
+
+    def proxy_open(self, req, proxy, type_):
+        host = req.get_host().split(':')[0]
+        if host in self.no_list:
+            return None
+
+        # work around a bug in Python < 2.4.2
+        # (it leaves a "\n" at the end of Proxy-authorization headers)
+        baseclass = req.__class__
+        class _request(baseclass):
+            def add_header(self, key, val):
+                if key.lower() == 'proxy-authorization':
+                    val = val.strip()
+                return baseclass.add_header(self, key, val)
+        req.__class__ = _request
+
+        return urllib2.ProxyHandler.proxy_open(self, req, proxy, type_)
+
+class httpsendfile(file):
+    def __len__(self):
+        return os.fstat(self.fileno()).st_size
+
+def _gen_sendfile(connection):
+    def _sendfile(self, data):
+        # send a file
+        if isinstance(data, httpsendfile):
+            # if auth required, some data sent twice, so rewind here
+            data.seek(0)
+            for chunk in util.filechunkiter(data):
+                connection.send(self, chunk)
+        else:
+            connection.send(self, data)
+    return _sendfile
+
+class httpconnection(keepalive.HTTPConnection):
+    # must be able to send big bundle as stream.
+    send = _gen_sendfile(keepalive.HTTPConnection)
+
+class httphandler(keepalive.HTTPHandler):
+    def http_open(self, req):
+        return self.do_open(httpconnection, req)
+
+    def __del__(self):
+        self.close_all()
+
+has_https = hasattr(urllib2, 'HTTPSHandler')
+if has_https:
+    class httpsconnection(httplib.HTTPSConnection):
+        response_class = keepalive.HTTPResponse
+        # must be able to send big bundle as stream.
+        send = _gen_sendfile(httplib.HTTPSConnection)
+
+    class httpshandler(keepalive.KeepAliveHandler, urllib2.HTTPSHandler):
+        def https_open(self, req):
+            return self.do_open(httpsconnection, req)
+
+# In python < 2.5 AbstractDigestAuthHandler raises a ValueError if
+# it doesn't know about the auth type requested.  This can happen if
+# somebody is using BasicAuth and types a bad password.
+class httpdigestauthhandler(urllib2.HTTPDigestAuthHandler):
+    def http_error_auth_reqed(self, auth_header, host, req, headers):
+        try:
+            return urllib2.HTTPDigestAuthHandler.http_error_auth_reqed(
+                        self, auth_header, host, req, headers)
+        except ValueError, inst:
+            arg = inst.args[0]
+            if arg.startswith("AbstractDigestAuthHandler doesn't know "):
+                return
+            raise
+
+def getauthinfo(path):
+    scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
+    if not urlpath:
+        urlpath = '/'
+    urlpath = quotepath(urlpath)
+    host, port, user, passwd = netlocsplit(netloc)
+
+    # urllib cannot handle URLs with embedded user or passwd
+    url = urlparse.urlunsplit((scheme, netlocunsplit(host, port),
+                              urlpath, query, frag))
+    if user:
+        netloc = host
+        if port:
+            netloc += ':' + port
+        # Python < 2.4.3 uses only the netloc to search for a password
+        authinfo = (None, (url, netloc), user, passwd or '')
+    else:
+        authinfo = None
+    return url, authinfo
+
+def opener(ui, authinfo=None):
+    '''
+    construct an opener suitable for urllib2
+    authinfo will be added to the password manager
+    '''
+    handlers = [httphandler()]
+    if has_https:
+        handlers.append(httpshandler())
+
+    handlers.append(proxyhandler(ui))
+
+    passmgr = passwordmgr(ui)
+    if authinfo is not None:
+        passmgr.add_password(*authinfo)
+        user, passwd = authinfo[2:4]
+        ui.debug(_('http auth: user %s, password %s\n') %
+                 (user, passwd and '*' * len(passwd) or 'not set'))
+
+    handlers.extend((urllib2.HTTPBasicAuthHandler(passmgr),
+                     httpdigestauthhandler(passmgr)))
+    opener = urllib2.build_opener(*handlers)
+
+    # 1.0 here is the _protocol_ version
+    opener.addheaders = [('User-agent', 'mercurial/proto-1.0')]
+    opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
+    return opener
+
+def open(ui, url, data=None):
+    scheme = urlparse.urlsplit(url)[0]
+    if not scheme:
+        url, authinfo = 'file://' + util.normpath(os.path.abspath(url)), None
+    else:
+        url, authinfo = getauthinfo(url)
+    return opener(ui, authinfo).open(url, data)
--- a/mercurial/util.py	Mon Oct 27 17:48:05 2008 +0100
+++ b/mercurial/util.py	Mon Oct 27 21:50:01 2008 +0100
@@ -15,7 +15,7 @@
 from i18n import _
 import cStringIO, errno, getpass, re, shutil, sys, tempfile
 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
-import imp, urlparse
+import imp
 
 # Python compatibility
 
@@ -1932,15 +1932,3 @@
 def uirepr(s):
     # Avoid double backslash in Windows path repr()
     return repr(s).replace('\\\\', '\\')
-
-def hidepassword(url):
-    '''hide user credential in a url string'''
-    scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
-    netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
-    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
-
-def removeauth(url):
-    '''remove all authentication information from a url string'''
-    scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
-    netloc = netloc[netloc.find('@')+1:]
-    return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))