--- a/mercurial/revset.py Tue May 08 22:43:44 2012 +0200
+++ b/mercurial/revset.py Tue May 22 14:37:20 2012 -0500
@@ -1308,6 +1308,27 @@
return w + wa, (op, x[1], ta)
return 1, x
+_aliasarg = ('func', ('symbol', '_aliasarg'))
+def _getaliasarg(tree):
+ """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
+ return X, None otherwise.
+ """
+ if (len(tree) == 3 and tree[:2] == _aliasarg
+ and tree[2][0] == 'string'):
+ return tree[2][1]
+ return None
+
+def _checkaliasarg(tree, known=None):
+ """Check tree contains no _aliasarg construct or only ones which
+ value is in known. Used to avoid alias placeholders injection.
+ """
+ if isinstance(tree, tuple):
+ arg = _getaliasarg(tree)
+ if arg is not None and (not known or arg not in known):
+ raise error.ParseError(_("not a function: %s") % '_aliasarg')
+ for t in tree:
+ _checkaliasarg(t, known)
+
class revsetalias(object):
funcre = re.compile('^([^(]+)\(([^)]+)\)$')
args = None
@@ -1324,7 +1345,9 @@
self.tree = ('func', ('symbol', m.group(1)))
self.args = [x.strip() for x in m.group(2).split(',')]
for arg in self.args:
- value = value.replace(arg, repr(arg))
+ # _aliasarg() is an unknown symbol only used separate
+ # alias argument placeholders from regular strings.
+ value = value.replace(arg, '_aliasarg(%r)' % (arg,))
else:
self.name = name
self.tree = ('symbol', name)
@@ -1332,6 +1355,8 @@
self.replacement, pos = parse(value)
if pos != len(value):
raise error.ParseError(_('invalid token'), pos)
+ # Check for placeholder injection
+ _checkaliasarg(self.replacement, self.args)
def _getalias(aliases, tree):
"""If tree looks like an unexpanded alias, return it. Return None
@@ -1352,13 +1377,14 @@
return None
def _expandargs(tree, args):
- """Replace all occurences of ('string', name) with the
- substitution value of the same name in args, recursively.
+ """Replace _aliasarg instances with the substitution value of the
+ same name in args, recursively.
"""
- if not isinstance(tree, tuple):
+ if not tree or not isinstance(tree, tuple):
return tree
- if len(tree) == 2 and tree[0] == 'string':
- return args.get(tree[1], tree)
+ arg = _getaliasarg(tree)
+ if arg is not None:
+ return args[arg]
return tuple(_expandargs(t, args) for t in tree)
def _expandaliases(aliases, tree, expanding):
@@ -1376,22 +1402,22 @@
raise error.ParseError(_('infinite expansion of revset alias "%s" '
'detected') % alias.name)
expanding.append(alias)
- result = alias.replacement
+ result = _expandaliases(aliases, alias.replacement, expanding)
+ expanding.pop()
if alias.args is not None:
l = getlist(tree[2])
if len(l) != len(alias.args):
raise error.ParseError(
_('invalid number of arguments: %s') % len(l))
+ l = [_expandaliases(aliases, a, []) for a in l]
result = _expandargs(result, dict(zip(alias.args, l)))
- # Recurse in place, the base expression may have been rewritten
- result = _expandaliases(aliases, result, expanding)
- expanding.pop()
else:
result = tuple(_expandaliases(aliases, t, expanding)
for t in tree)
return result
def findaliases(ui, tree):
+ _checkaliasarg(tree)
aliases = {}
for k, v in ui.configitems('revsetalias'):
alias = revsetalias(k, v)
--- a/tests/test-revset.t Tue May 08 22:43:44 2012 +0200
+++ b/tests/test-revset.t Tue May 22 14:37:20 2012 -0500
@@ -527,6 +527,27 @@
hg: parse error: infinite expansion of revset alias "recurse1" detected
[255]
+ $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
+ $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
+ $ try "level2(level1(1, 2), 3)"
+ (func
+ ('symbol', 'level2')
+ (list
+ (func
+ ('symbol', 'level1')
+ (list
+ ('symbol', '1')
+ ('symbol', '2')))
+ ('symbol', '3')))
+ (or
+ ('symbol', '3')
+ (or
+ ('symbol', '1')
+ ('symbol', '2')))
+ 3
+ 1
+ 2
+
test nesting and variable passing
$ echo 'nested($1) = nested2($1)' >> .hg/hgrc
@@ -565,6 +586,19 @@
abort: unknown revision '$1'!
[255]
+ $ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
+ $ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
+ $ try 'callinjection2(2:5)'
+ (func
+ ('symbol', 'callinjection2')
+ (range
+ ('symbol', '2')
+ ('symbol', '5')))
+ hg: parse error: not a function: _aliasarg
+ [255]
+ >>> data = file('.hg/hgrc', 'rb').read()
+ >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
+
$ try 'd(2:5)'
(func
('symbol', 'd')