Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> 

# 

# This file is part of Ansible 

# 

# Ansible is free software: you can redistribute it and/or modify 

# it under the terms of the GNU General Public License as published by 

# the Free Software Foundation, either version 3 of the License, or 

# (at your option) any later version. 

# 

# Ansible is distributed in the hope that it will be useful, 

# but WITHOUT ANY WARRANTY; without even the implied warranty of 

# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

# GNU General Public License for more details. 

# 

# You should have received a copy of the GNU General Public License 

# along with Ansible. If not, see <http://www.gnu.org/licenses/>. 

 

from __future__ import (absolute_import, division, print_function) 

__metaclass__ = type 

 

import ast 

import sys 

 

from ansible import constants as C 

from ansible.module_utils.six import string_types 

from ansible.module_utils.six.moves import builtins 

from ansible.plugins.loader import filter_loader, test_loader 

 

 

def safe_eval(expr, locals=None, include_exceptions=False): 

''' 

This is intended for allowing things like: 

with_items: a_list_variable 

 

Where Jinja2 would return a string but we do not want to allow it to 

call functions (outside of Jinja2, where the env is constrained). 

 

Based on: 

http://stackoverflow.com/questions/12523516/using-ast-and-whitelists-to-make-pythons-eval-safe 

''' 

locals = {} if locals is None else locals 

 

# define certain JSON types 

# eg. JSON booleans are unknown to python eval() 

JSON_TYPES = { 

'false': False, 

'null': None, 

'true': True, 

} 

 

# this is the whitelist of AST nodes we are going to 

# allow in the evaluation. Any node type other than 

# those listed here will raise an exception in our custom 

# visitor class defined below. 

SAFE_NODES = set( 

( 

ast.Add, 

ast.BinOp, 

# ast.Call, 

ast.Compare, 

ast.Dict, 

ast.Div, 

ast.Expression, 

ast.List, 

ast.Load, 

ast.Mult, 

ast.Num, 

ast.Name, 

ast.Str, 

ast.Sub, 

ast.USub, 

ast.Tuple, 

ast.UnaryOp, 

) 

) 

 

# AST node types were expanded after 2.6 

78 ↛ 86line 78 didn't jump to line 86, because the condition on line 78 was never false if sys.version_info[:2] >= (2, 7): 

SAFE_NODES.update( 

set( 

(ast.Set,) 

) 

) 

 

# And in Python 3.4 too 

86 ↛ 87line 86 didn't jump to line 87, because the condition on line 86 was never true if sys.version_info[:2] >= (3, 4): 

SAFE_NODES.update( 

set( 

(ast.NameConstant,) 

) 

) 

 

filter_list = [] 

for filter in filter_loader.all(): 

filter_list.extend(filter.filters().keys()) 

 

test_list = [] 

for test in test_loader.all(): 

test_list.extend(test.tests().keys()) 

 

CALL_WHITELIST = C.DEFAULT_CALLABLE_WHITELIST + filter_list + test_list 

 

class CleansingNodeVisitor(ast.NodeVisitor): 

def generic_visit(self, node, inside_call=False): 

105 ↛ 106line 105 didn't jump to line 106, because the condition on line 105 was never true if type(node) not in SAFE_NODES: 

raise Exception("invalid expression (%s)" % expr) 

107 ↛ 108line 107 didn't jump to line 108, because the condition on line 107 was never true elif isinstance(node, ast.Call): 

inside_call = True 

109 ↛ 113line 109 didn't jump to line 113, because the condition on line 109 was never true elif isinstance(node, ast.Name) and inside_call: 

# Disallow calls to builtin functions that we have not vetted 

# as safe. Other functions are excluded by setting locals in 

# the call to eval() later on 

if hasattr(builtins, node.id) and node.id not in CALL_WHITELIST: 

raise Exception("invalid function: %s" % node.id) 

# iterate over all child nodes 

for child_node in ast.iter_child_nodes(node): 

self.generic_visit(child_node, inside_call) 

 

119 ↛ 121line 119 didn't jump to line 121, because the condition on line 119 was never true if not isinstance(expr, string_types): 

# already templated to a datastructure, perhaps? 

if include_exceptions: 

return (expr, None) 

return expr 

 

cnv = CleansingNodeVisitor() 

try: 

parsed_tree = ast.parse(expr, mode='eval') 

cnv.visit(parsed_tree) 

compiled = compile(parsed_tree, expr, 'eval') 

# Note: passing our own globals and locals here constrains what 

# callables (and other identifiers) are recognized. this is in 

# addition to the filtering of builtins done in CleansingNodeVisitor 

result = eval(compiled, JSON_TYPES, dict(locals)) 

 

135 ↛ 138line 135 didn't jump to line 138, because the condition on line 135 was never false if include_exceptions: 

return (result, None) 

else: 

return result 

except SyntaxError as e: 

# special handling for syntax errors, we just return 

# the expression string back as-is to support late evaluation 

if include_exceptions: 

return (expr, None) 

return expr 

except Exception as e: 

if include_exceptions: 

return (expr, e) 

return expr