# HG changeset patch # User Matt Mackall # Date 2011-08-01 23:10:05 # Node ID d0424f39984c6d623dc8c55b85de66f15e27abd6 # Parent a31b8e03af28c5c8c7f2c2c8aa935d85561dd2bb # Parent 9991f8b19ff3fe353690646b2b2bb77e4db4981e merge with stable diff --git a/.hgsigs b/.hgsigs --- a/.hgsigs +++ b/.hgsigs @@ -40,3 +40,4 @@ b032bec2c0a651ca0ddecb65714bfe6770f67d70 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 0 iD8DBQBNvTy4ywK+sNU5EO8RAmp8AJ9QnxK4jTJ7G722MyeBxf0UXEdGwACgtlM7BKtNQfbEH/fOW5y+45W88VI= 733af5d9f6b22387913e1d11350fb8cb7c1487dd 0 iD8DBQBN5q/8ywK+sNU5EO8RArRGAKCNGT94GKIYtSuwZ57z1sQbcw6uLACfffpbMV4NAPMl8womAwg+7ZPKnIU= de9eb6b1da4fc522b1cab16d86ca166204c24f25 0 iD8DBQBODhfhywK+sNU5EO8RAr2+AJ4ugbAj8ae8/K0bYZzx3sascIAg1QCeK3b+zbbVVqd3b7CDpwFnaX8kTd4= +4a43e23b8c55b4566b8200bf69fe2158485a2634 0 iD8DBQBONzIMywK+sNU5EO8RAj5SAJ0aPS3+JHnyI6bHB2Fl0LImbDmagwCdGbDLp1S7TFobxXudOH49bX45Iik= diff --git a/.hgtags b/.hgtags --- a/.hgtags +++ b/.hgtags @@ -52,3 +52,4 @@ b032bec2c0a651ca0ddecb65714bfe6770f67d70 3cb1e95676ad089596bd81d0937cad37d6e3b7fb 1.8.3 733af5d9f6b22387913e1d11350fb8cb7c1487dd 1.8.4 de9eb6b1da4fc522b1cab16d86ca166204c24f25 1.9 +4a43e23b8c55b4566b8200bf69fe2158485a2634 1.9.1 diff --git a/mercurial/help/config.txt b/mercurial/help/config.txt --- a/mercurial/help/config.txt +++ b/mercurial/help/config.txt @@ -266,7 +266,9 @@ Supported arguments: Optional. Username to authenticate with. If not given, and the remote site requires basic or digest authentication, the user will be prompted for it. Environment variables are expanded in the - username letting you do ``foo.username = $USER``. + username letting you do ``foo.username = $USER``. If the URI + includes a username, only ``[auth]`` entries with a matching + username or without a username will be considered. ``password`` Optional. Password to authenticate with. If not given, and the @@ -1158,6 +1160,13 @@ The full set of options is: be present in this list. The contents of the allow_push list are examined after the deny_push list. +``guessmime`` + Control MIME types for raw download of file content. + Set to True to let hgweb guess the content type from the file + extension. This will serve HTML files as ``text/html`` and might + allow cross-site scripting attacks when serving untrusted + repositories. Default is False. + ``allow_read`` If the user has not already been denied repository access due to the contents of deny_read, this list determines whether to grant diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py --- a/mercurial/hgweb/webcommands.py +++ b/mercurial/hgweb/webcommands.py @@ -32,6 +32,8 @@ def log(web, req, tmpl): return changelog(web, req, tmpl) def rawfile(web, req, tmpl): + guessmime = web.configbool('web', 'guessmime', False) + path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) if not path: content = manifest(web, req, tmpl) @@ -50,9 +52,11 @@ def rawfile(web, req, tmpl): path = fctx.path() text = fctx.data() - mt = mimetypes.guess_type(path)[0] - if mt is None: - mt = binary(text) and 'application/octet-stream' or 'text/plain' + mt = 'application/binary' + if guessmime: + mt = mimetypes.guess_type(path)[0] + if mt is None: + mt = binary(text) and 'application/binary' or 'text/plain' if mt.startswith('text/'): mt += '; charset="%s"' % encoding.encoding diff --git a/mercurial/httpconnection.py b/mercurial/httpconnection.py --- a/mercurial/httpconnection.py +++ b/mercurial/httpconnection.py @@ -72,10 +72,19 @@ 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 bestauth = None for group, auth in config.iteritems(): + if user and user != auth.get('username', user): + # If a username was set in the URI, the entry username + # must either match it or be unset + continue prefix = auth.get('prefix') if not prefix: continue @@ -85,9 +94,14 @@ def readauthforuri(ui, uri): else: schemes = (auth.get('schemes') or 'https').split() if (prefix == '*' or hostpath.startswith(prefix)) and \ - len(prefix) > bestlen and scheme in schemes: + (len(prefix) > bestlen or (len(prefix) == bestlen and \ + not bestuser and 'username' in auth)) \ + and scheme in schemes: bestlen = len(prefix) bestauth = group, auth + bestuser = auth.get('username') + if user and not bestuser: + auth['username'] = user return bestauth # Mercurial (at least until we can remove the old codepath) requires diff --git a/mercurial/url.py b/mercurial/url.py --- a/mercurial/url.py +++ b/mercurial/url.py @@ -25,7 +25,7 @@ class passwordmgr(urllib2.HTTPPasswordMg self._writedebug(user, passwd) return (user, passwd) - if not user: + if not user or not passwd: res = httpconnectionmod.readauthforuri(self.ui, authuri) if res: group, auth = res 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,5 +1,5 @@ from mercurial import demandimport; demandimport.enable() -from mercurial import ui +from mercurial import ui, util from mercurial import url from mercurial.error import Abort @@ -19,13 +19,16 @@ def dumpdict(dict): return '{' + ', '.join(['%s: %s' % (k, dict[k]) for k in sorted(dict.iterkeys())]) + '}' -def test(auth): +def test(auth, urls=None): print 'CFG:', dumpdict(auth) prefixes = set() for k in auth: prefixes.add(k.split('.', 1)[0]) for p in prefixes: - auth.update({p + '.username': p, p + '.password': p}) + for name in ('.username', '.password'): + if (p + name) not in auth: + auth[p + name] = p + auth = dict((k, v) for k, v in auth.iteritems() if v is not None) ui = writeauth(auth) @@ -33,16 +36,26 @@ def test(auth): print 'URI:', uri try: pm = url.passwordmgr(ui) + authinfo = util.url(uri).authinfo()[1] + if authinfo is not None: + pm.add_password(*authinfo) print ' ', pm.find_user_password('test', uri) except Abort, e: print 'abort' - _test('http://example.org/foo') - _test('http://example.org/foo/bar') - _test('http://example.org/bar') - _test('https://example.org/foo') - _test('https://example.org/foo/bar') - _test('https://example.org/bar') + if not urls: + urls = [ + 'http://example.org/foo', + 'http://example.org/foo/bar', + 'http://example.org/bar', + 'https://example.org/foo', + 'https://example.org/foo/bar', + 'https://example.org/bar', + 'https://x@example.org/bar', + 'https://y@example.org/bar', + ] + for u in urls: + _test(u) print '\n*** Test in-uri schemes\n' @@ -62,3 +75,23 @@ test({'x.prefix': 'http://example.org/fo test({'x.prefix': 'http://example.org/foo', 'y.prefix': 'http://example.org/foo/bar'}) test({'x.prefix': '*', 'y.prefix': 'https://example.org/bar'}) + +print '\n*** Test user matching\n' +test({'x.prefix': 'http://example.org/foo', + 'x.username': None, + 'x.password': 'xpassword'}, + urls=['http://y@example.org/foo']) +test({'x.prefix': 'http://example.org/foo', + 'x.username': None, + 'x.password': 'xpassword', + 'y.prefix': 'http://example.org/foo', + 'y.username': 'y', + 'y.password': 'ypassword'}, + urls=['http://y@example.org/foo']) +test({'x.prefix': 'http://example.org/foo/bar', + 'x.username': None, + 'x.password': 'xpassword', + 'y.prefix': 'http://example.org/foo', + 'y.username': 'y', + 'y.password': 'ypassword'}, + urls=['http://y@example.org/foo/bar']) 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 @@ -14,6 +14,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: https://example.org} URI: http://example.org/foo abort @@ -27,6 +31,10 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('x', 'x') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + abort CFG: {x.prefix: http://example.org, x.schemes: https} URI: http://example.org/foo ('x', 'x') @@ -40,6 +48,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: https://example.org, x.schemes: http} URI: http://example.org/foo abort @@ -53,6 +65,10 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('x', 'x') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + abort *** Test separately configured schemes @@ -69,6 +85,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: example.org, x.schemes: https} URI: http://example.org/foo abort @@ -82,6 +102,10 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('x', 'x') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + abort CFG: {x.prefix: example.org, x.schemes: http https} URI: http://example.org/foo ('x', 'x') @@ -95,6 +119,10 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('x', 'x') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + abort *** Test prefix matching @@ -111,6 +139,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: http://example.org/foo, y.prefix: http://example.org/foo/bar} URI: http://example.org/foo ('x', 'x') @@ -124,6 +156,10 @@ URI: https://example.org/foo/bar abort URI: https://example.org/bar abort +URI: https://x@example.org/bar + abort +URI: https://y@example.org/bar + abort CFG: {x.prefix: *, y.prefix: https://example.org/bar} URI: http://example.org/foo abort @@ -137,3 +173,19 @@ URI: https://example.org/foo/bar ('x', 'x') URI: https://example.org/bar ('y', 'y') +URI: https://x@example.org/bar + ('x', 'x') +URI: https://y@example.org/bar + ('y', 'y') + +*** Test user matching + +CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None} +URI: http://y@example.org/foo + ('y', 'xpassword') +CFG: {x.password: xpassword, x.prefix: http://example.org/foo, x.username: None, y.password: ypassword, y.prefix: http://example.org/foo, y.username: y} +URI: http://y@example.org/foo + ('y', 'ypassword') +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') diff --git a/tests/test-hgweb-raw.t b/tests/test-hgweb-raw.t --- a/tests/test-hgweb-raw.t +++ b/tests/test-hgweb-raw.t @@ -22,6 +22,28 @@ Test raw style of hgweb $ sleep 1 # wait for server to scream and die $ cat getoutput.txt 200 Script output follows + content-type: application/binary + content-length: 157 + content-disposition: inline; filename="some \"text\".txt" + + This is just some random text + that will go inside the file and take a few lines. + It is very boring to read, but computers don't + care about things like that. + $ cat access.log error.log + 127.0.0.1 - - [*] "GET /?f=a23bf1310f6e;file=sub/some%20%22text%22.txt;style=raw HTTP/1.1" 200 - (glob) + + $ rm access.log error.log + $ hg serve -p $HGPORT -A access.log -E error.log -d --pid-file=hg.pid \ + > --config web.guessmime=True + + $ cat hg.pid >> $DAEMON_PIDS + $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/?f=a23bf1310f6e;file=sub/some%20%22text%22.txt;style=raw' content-type content-length content-disposition) >getoutput.txt & + $ sleep 5 + $ kill `cat hg.pid` + $ sleep 1 # wait for server to scream and die + $ cat getoutput.txt + 200 Script output follows content-type: text/plain; charset="ascii" content-length: 157 content-disposition: inline; filename="some \"text\".txt"