diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py --- a/mercurial/dispatch.py +++ b/mercurial/dispatch.py @@ -357,6 +357,15 @@ def addaliases(ui, cmdtable): # but only if they have been defined prior to the current definition. for alias, definition in ui.configitems('alias'): aliasdef = cmdalias(alias, definition, cmdtable) + + try: + olddef = cmdtable[aliasdef.cmd][0] + if olddef.definition == aliasdef.definition: + continue + except (KeyError, AttributeError): + # definition might not exist or it might not be a cmdalias + pass + cmdtable[aliasdef.cmd] = (aliasdef, aliasdef.opts, aliasdef.help) if aliasdef.norepo: commands.norepo += ' %s' % alias diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py --- a/mercurial/httpconnection.py +++ b/mercurial/httpconnection.py @@ -58,7 +58,7 @@ class httpsendfile(object): return self._len # moved here from url.py to avoid a cycle -def readauthforuri(ui, uri): +def readauthforuri(ui, uri, user): # Read configuration config = dict() for key, val in ui.configitems('auth'): @@ -72,10 +72,6 @@ def readauthforuri(ui, uri): gdict[setting] = val # Find the best match - uri = util.url(uri) - user = uri.user - uri.user = uri.password = None - uri = str(uri) scheme, hostpath = uri.split('://', 1) bestuser = None bestlen = 0 @@ -238,7 +234,11 @@ class http2handler(urllib2.HTTPHandler, return self.do_open(HTTPConnection, req, False) def https_open(self, req): - res = readauthforuri(self.ui, req.get_full_url()) + # req.get_full_url() does not contain credentials and we may + # need them to match the certificates. + url = req.get_full_url() + user, password = self.pwmgr.find_stored_password(url) + res = readauthforuri(self.ui, url, user) if res: group, auth = res self.auth = auth diff --git a/mercurial/url.py b/mercurial/url.py --- a/mercurial/url.py +++ b/mercurial/url.py @@ -26,7 +26,7 @@ class passwordmgr(urllib2.HTTPPasswordMg return (user, passwd) if not user or not passwd: - res = httpconnectionmod.readauthforuri(self.ui, authuri) + res = httpconnectionmod.readauthforuri(self.ui, authuri, user) if res: group, auth = res user, passwd = auth.get('username'), auth.get('password') @@ -53,6 +53,10 @@ class passwordmgr(urllib2.HTTPPasswordMg msg = _('http auth: user %s, password %s\n') self.ui.debug(msg % (user, passwd and '*' * len(passwd) or 'not set')) + def find_stored_password(self, authuri): + return urllib2.HTTPPasswordMgrWithDefaultRealm.find_user_password( + self, None, authuri) + class proxyhandler(urllib2.ProxyHandler): def __init__(self, ui): proxyurl = ui.config("http_proxy", "host") or os.getenv('http_proxy') @@ -342,7 +346,11 @@ if has_https: return keepalive.KeepAliveHandler._start_transaction(self, h, req) def https_open(self, req): - res = httpconnectionmod.readauthforuri(self.ui, req.get_full_url()) + # req.get_full_url() does not contain credentials and we may + # need them to match the certificates. + url = req.get_full_url() + user, password = self.pwmgr.find_stored_password(url) + res = httpconnectionmod.readauthforuri(self.ui, url, user) if res: group, auth = res self.auth = auth diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -1468,6 +1468,8 @@ class url(object): path = None if not self.host: self.host = None + # path of file:///d is /d + # path of file:///d:/ is d:/, not /d:/ if path and not hasdriveletter(path): path = '/' + path @@ -1587,7 +1589,9 @@ class url(object): self.user, self.passwd = user, passwd if not self.user: return (s, None) - return (s, (None, (str(self), self.host), + # authinfo[1] is passed to urllib2 password manager, and its URIs + # must not contain credentials. + return (s, (None, (s, self.host), self.user, self.passwd or '')) def isabs(self): @@ -1610,11 +1614,6 @@ class url(object): path = self._hostport + '/' + self.path elif self.host is not None and self.path: path = '/' + path - # We also need to handle the case of file:///C:/, which - # should return C:/, not /C:/. - elif hasdriveletter(path): - # Strip leading slash from paths with drive names - return path[1:] return path return self._origpath diff --git a/tests/test-hgweb-auth.py b/tests/test-hgweb-auth.py --- a/tests/test-hgweb-auth.py +++ b/tests/test-hgweb-auth.py @@ -1,4 +1,5 @@ from mercurial import demandimport; demandimport.enable() +import urllib2 from mercurial import ui, util from mercurial import url from mercurial.error import Abort @@ -36,10 +37,10 @@ def test(auth, urls=None): print 'URI:', uri try: pm = url.passwordmgr(ui) - authinfo = util.url(uri).authinfo()[1] + u, authinfo = util.url(uri).authinfo() if authinfo is not None: pm.add_password(*authinfo) - print ' ', pm.find_user_password('test', uri) + print ' ', pm.find_user_password('test', u) except Abort, e: print 'abort' @@ -95,3 +96,12 @@ test({'x.prefix': 'http://example.org/fo 'y.username': 'y', 'y.password': 'ypassword'}, urls=['http://y@example.org/foo/bar']) + +def testauthinfo(fullurl, authurl): + print 'URIs:', fullurl, authurl + pm = urllib2.HTTPPasswordMgrWithDefaultRealm() + pm.add_password(*util.url(fullurl).authinfo()[1]) + print pm.find_user_password('test', authurl) + +print '\n*** Test urllib2 and util.url\n' +testauthinfo('http://user@example.com:8080/foo', 'http://example.com:8080/foo') diff --git a/tests/test-hgweb-auth.py.out b/tests/test-hgweb-auth.py.out --- a/tests/test-hgweb-auth.py.out +++ b/tests/test-hgweb-auth.py.out @@ -189,3 +189,8 @@ URI: http://y@example.org/foo CFG: {x.password: xpassword, x.prefix: http://example.org/foo/bar, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y} URI: http://y@example.org/foo/bar ('y', 'xpassword') + +*** Test urllib2 and util.url + +URIs: http://user@example.com:8080/foo http://example.com:8080/foo +('user', '') diff --git a/tests/test-http.t b/tests/test-http.t --- a/tests/test-http.t +++ b/tests/test-http.t @@ -110,6 +110,55 @@ clone from invalid URL abort: HTTP Error 404: Not Found [255] +test http authentication + + $ cd test + $ cat << EOT > userpass.py + > import base64 + > from mercurial.hgweb import common + > def perform_authentication(hgweb, req, op): + > auth = req.env.get('HTTP_AUTHORIZATION') + > if not auth: + > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who', + > [('WWW-Authenticate', 'Basic Realm="mercurial"')]) + > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']: + > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no') + > def extsetup(): + > common.permhooks.insert(0, perform_authentication) + > EOT + $ hg --config extensions.x=userpass.py serve -p $HGPORT2 -d --pid-file=pid + $ cat pid >> $DAEMON_PIDS + + $ hg id http://localhost:$HGPORT2/ + abort: http authorization required + [255] + $ hg id http://user@localhost:$HGPORT2/ + abort: http authorization required + [255] + $ hg id http://user:pass@localhost:$HGPORT2/ + 5fed3813f7f5 + $ echo '[auth]' >> .hg/hgrc + $ echo 'l.schemes=http' >> .hg/hgrc + $ echo 'l.prefix=lo' >> .hg/hgrc + $ echo 'l.username=user' >> .hg/hgrc + $ echo 'l.password=pass' >> .hg/hgrc + $ hg id http://localhost:$HGPORT2/ + 5fed3813f7f5 + $ hg id http://localhost:$HGPORT2/ + 5fed3813f7f5 + $ hg id http://user@localhost:$HGPORT2/ + 5fed3813f7f5 + $ hg id http://user:pass@localhost:$HGPORT2/ + 5fed3813f7f5 + $ hg id http://user2@localhost:$HGPORT2/ + abort: http authorization required + [255] + $ hg id http://user:pass2@localhost:$HGPORT2/ + abort: HTTP Error 403: no + [255] + + $ cd .. + check error log $ cat error.log diff --git a/tests/test-url.py b/tests/test-url.py --- a/tests/test-url.py +++ b/tests/test-url.py @@ -204,18 +204,32 @@ def test_url(): >>> str(u) 'file:///foo/bar/baz' + >>> u.localpath() + '/foo/bar/baz' >>> u = url('file:///foo/bar/baz') >>> u >>> str(u) 'file:///foo/bar/baz' + >>> u.localpath() + '/foo/bar/baz' + + >>> u = url('file:///f:oo/bar/baz') + >>> u + + >>> str(u) + 'file:f%3Aoo/bar/baz' + >>> u.localpath() + 'f:oo/bar/baz' >>> u = url('file:foo/bar/baz') >>> u >>> str(u) 'file:foo/bar/baz' + >>> u.localpath() + 'foo/bar/baz' """ doctest.testmod(optionflags=doctest.NORMALIZE_WHITESPACE)