mercurial/revset.py
changeset 16778 2ac08d8b21aa
parent 16755 d0b9ebba41e9
parent 16772 30e46d7138de
child 16803 107a3270a24a
--- 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)