merge with stable
authorMatt Mackall <mpm@selenic.com>
Tue, 22 May 2012 14:37:20 -0500
changeset 16778 2ac08d8b21aa
parent 16777 058e14da7044 (current diff)
parent 16772 30e46d7138de (diff)
child 16779 67bfe7f64e57
child 16792 ad394c897b16
merge with stable
mercurial/revset.py
tests/test-revset.t
--- 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')