# HG changeset patch # User Patrick Mezard # Date 1337440709 -7200 # Node ID 2f3317d53d5154639b4069be3aa1fa46abed046d # Parent 23a125545c3d6108648dc48986417261aabcfa5d revset: explicitely tag alias arguments for expansion The current revset alias expansion code works like: 1- Get the replacement tree 2- Substitute the variables in the replacement tree 3- Expand the replacement tree It makes it easy to substitute alias arguments because the placeholders are always replaced before the updated replacement tree is expanded again. Unfortunately, to fix other alias expansion issues, we need to reorder the sequence and delay the argument substitution. To solve this, a new "virtual" construct called _aliasarg() is introduced and injected when parsing the aliases definitions. Only _aliasarg() will be substituted in the argument expansion phase instead of all regular matching string. We also check user inputs do not contain unexpected _aliasarg() instances to avoid argument injections. diff -r 23a125545c3d -r 2f3317d53d51 mercurial/revset.py --- a/mercurial/revset.py Mon May 21 14:24:24 2012 -0500 +++ b/mercurial/revset.py Sat May 19 17:18:29 2012 +0200 @@ -1283,6 +1283,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 @@ -1299,7 +1320,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) @@ -1307,6 +1330,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 @@ -1327,13 +1352,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): @@ -1367,6 +1393,7 @@ return result def findaliases(ui, tree): + _checkaliasarg(tree) aliases = {} for k, v in ui.configitems('revsetalias'): alias = revsetalias(k, v) diff -r 23a125545c3d -r 2f3317d53d51 tests/test-revset.t --- a/tests/test-revset.t Mon May 21 14:24:24 2012 -0500 +++ b/tests/test-revset.t Sat May 19 17:18:29 2012 +0200 @@ -558,6 +558,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')