~canonical-ci-engineering/ubuntu-ci-services-itself/ansible

« back to all changes in this revision

Viewing changes to lib/ansible/utils/__init__.py

  • Committer: Package Import Robot
  • Author(s): Harlan Lieberman-Berg
  • Date: 2014-04-01 22:00:24 UTC
  • mfrom: (1.1.9)
  • Revision ID: package-import@ubuntu.com-20140401220024-jkxyhf2ggcv7xmqa
Tags: 1.5.4+dfsg-1
* Pull missing manpages from upstream development branch.
* New upstream version 1.5.4, security update.
* Add patch to correct directory_mode functionality. (Closes: #743027)

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
from ansible.utils import template
30
30
from ansible.callbacks import display
31
31
import ansible.constants as C
 
32
import ast
32
33
import time
33
34
import StringIO
34
35
import stat
945
946
            return False
946
947
    return True
947
948
 
948
 
def safe_eval(str, locals=None, include_exceptions=False):
 
949
def safe_eval(expr, locals={}, include_exceptions=False):
949
950
    '''
950
951
    this is intended for allowing things like:
951
952
    with_items: a_list_variable
952
953
    where Jinja2 would return a string
953
954
    but we do not want to allow it to call functions (outside of Jinja2, where
954
955
    the env is constrained)
 
956
 
 
957
    Based on:
 
958
    http://stackoverflow.com/questions/12523516/using-ast-and-whitelists-to-make-pythons-eval-safe
955
959
    '''
956
 
    # FIXME: is there a more native way to do this?
957
 
 
958
 
    def is_set(var):
959
 
        return not var.startswith("$") and not '{{' in var
960
 
 
961
 
    def is_unset(var):
962
 
        return var.startswith("$") or '{{' in var
963
 
 
964
 
    # do not allow method calls to modules
965
 
    if not isinstance(str, basestring):
 
960
 
 
961
    # this is the whitelist of AST nodes we are going to 
 
962
    # allow in the evaluation. Any node type other than 
 
963
    # those listed here will raise an exception in our custom
 
964
    # visitor class defined below.
 
965
    SAFE_NODES = set(
 
966
        (
 
967
            ast.Expression,
 
968
            ast.Compare,
 
969
            ast.Str,
 
970
            ast.List,
 
971
            ast.Tuple,
 
972
            ast.Dict,
 
973
            ast.Call,
 
974
            ast.Load,
 
975
            ast.BinOp,
 
976
            ast.UnaryOp,
 
977
            ast.Num,
 
978
            ast.Name,
 
979
            ast.Add,
 
980
            ast.Sub,
 
981
            ast.Mult,
 
982
            ast.Div,
 
983
        )
 
984
    )
 
985
 
 
986
    # AST node types were expanded after 2.6
 
987
    if not sys.version.startswith('2.6'):
 
988
        SAFE_NODES.union(
 
989
            set(
 
990
                (ast.Set,)
 
991
            )
 
992
        )
 
993
 
 
994
    # builtin functions that are not safe to call
 
995
    INVALID_CALLS = (
 
996
       'classmethod', 'compile', 'delattr', 'eval', 'execfile', 'file',
 
997
       'filter', 'help', 'input', 'object', 'open', 'raw_input', 'reduce',
 
998
       'reload', 'repr', 'setattr', 'staticmethod', 'super', 'type',
 
999
    )
 
1000
 
 
1001
    class CleansingNodeVisitor(ast.NodeVisitor):
 
1002
        def generic_visit(self, node):
 
1003
            if type(node) not in SAFE_NODES:
 
1004
                #raise Exception("invalid expression (%s) type=%s" % (expr, type(node)))
 
1005
                raise Exception("invalid expression (%s)" % expr)
 
1006
            super(CleansingNodeVisitor, self).generic_visit(node)
 
1007
        def visit_Call(self, call):
 
1008
            if call.func.id in INVALID_CALLS:
 
1009
                raise Exception("invalid function: %s" % call.func.id)
 
1010
 
 
1011
    if not isinstance(expr, basestring):
966
1012
        # already templated to a datastructure, perhaps?
967
1013
        if include_exceptions:
968
 
            return (str, None)
969
 
        return str
970
 
    if re.search(r'\w\.\w+\(', str):
971
 
        if include_exceptions:
972
 
            return (str, None)
973
 
        return str
974
 
    # do not allow imports
975
 
    if re.search(r'import \w+', str):
976
 
        if include_exceptions:
977
 
            return (str, None)
978
 
        return str
 
1014
            return (expr, None)
 
1015
        return expr
 
1016
 
979
1017
    try:
980
 
        result = None
981
 
        if not locals:
982
 
            result = eval(str)
983
 
        else:
984
 
            result = eval(str, None, locals)
 
1018
        parsed_tree = ast.parse(expr, mode='eval')
 
1019
        cnv = CleansingNodeVisitor()
 
1020
        cnv.visit(parsed_tree)
 
1021
        compiled = compile(parsed_tree, expr, 'eval')
 
1022
        result = eval(compiled, {}, locals)
 
1023
 
985
1024
        if include_exceptions:
986
1025
            return (result, None)
987
1026
        else:
988
1027
            return result
 
1028
    except SyntaxError, e:
 
1029
        # special handling for syntax errors, we just return
 
1030
        # the expression string back as-is
 
1031
        if include_exceptions:
 
1032
            return (expr, None)
 
1033
        return expr
989
1034
    except Exception, e:
990
1035
        if include_exceptions:
991
 
            return (str, e)
992
 
        return str
 
1036
            return (expr, e)
 
1037
        return expr
993
1038
 
994
1039
 
995
1040
def listify_lookup_plugin_terms(terms, basedir, inject):
1001
1046
        #    with_items: {{ alist }}
1002
1047
 
1003
1048
        stripped = terms.strip()
1004
 
        if not (stripped.startswith('{') or stripped.startswith('[')) and not stripped.startswith("/"):
 
1049
        if not (stripped.startswith('{') or stripped.startswith('[')) and not stripped.startswith("/") and not stripped.startswith('set(['):
1005
1050
            # if not already a list, get ready to evaluate with Jinja2
1006
1051
            # not sure why the "/" is in above code :)
1007
1052
            try: