1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 # Copyright (C) 2004-2010 OpenERP s.a. (<http://www.openerp.com>).
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 ##############################################################################
21 safe_eval module - methods intended to provide more restricted alternatives to
22 evaluate simple and/or untrusted code.
24 Methods in this module are typically used as alternatives to eval() to parse
25 OpenERP domain strings, conditions and expressions, mostly based on locals
26 condition/math builtins.
29 # Module partially ripped from/inspired by several different sources:
30 # - http://code.activestate.com/recipes/286134/
31 # - safe_eval in lp:~xrg/openobject-server/optimize-5.0
32 # - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
33 # - python 2.6's ast.literal_eval
35 from opcode import HAVE_ARGUMENT, opmap, opname
36 from types import CodeType
40 __all__ = ['test_expr', 'literal_eval', 'safe_eval', 'const_eval', 'ext_eval' ]
42 _CONST_OPCODES = set(opmap[x] for x in [
43 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP','POP_BLOCK','SETUP_LOOP',
44 'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
45 'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR'] if x in opmap)
47 _EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
48 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT',
49 'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY',
50 'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE',
51 'BINARY_MODULO', 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_SUBSCR',
52 'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR',
53 'BINARY_OR'] if x in opmap))
55 _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
56 'STORE_MAP', 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR',
57 'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'JUMP_ABSOLUTE',
58 'DELETE_NAME', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE','MAKE_FUNCTION','JUMP_FORWARD'
61 _logger = logging.getLogger('safe_eval')
63 def _get_opcodes(codeobj):
64 """_get_opcodes(codeobj) -> [opcodes]
66 Extract the actual opcodes as a list from a code object
68 >>> c = compile("[1 + 2, (1,2)]", "", "eval")
70 [100, 100, 23, 100, 100, 102, 103, 83]
74 byte_codes = codeobj.co_code
75 while i < len(byte_codes):
76 code = ord(byte_codes[i])
78 if code >= HAVE_ARGUMENT:
84 def test_expr(expr, allowed_codes, mode="eval"):
85 """test_expr(expression, allowed_codes[, mode]) -> code_object
87 Test that the expression contains only the allowed opcodes.
88 If the expression is valid and contains only allowed codes,
89 return the compiled code object.
90 Otherwise raise a ValueError, a Syntax Error or TypeError accordingly.
94 # eval() does not like leading/trailing whitespace
96 code_obj = compile(expr, "", mode)
97 except (SyntaxError, TypeError):
98 _logger.debug('Invalid eval expression', exc_info=True)
101 _logger.debug('Disallowed or invalid eval expression', exc_info=True)
102 raise ValueError("%s is not a valid expression" % expr)
103 for code in _get_opcodes(code_obj):
104 if code not in allowed_codes:
105 raise ValueError("opcode %s not allowed (%r)" % (opname[code], expr))
109 def const_eval(expr):
110 """const_eval(expression) -> value
112 Safe Python constant evaluation
114 Evaluates a string that contains an expression describing
115 a Python constant. Strings that are not valid Python expressions
116 or that contain other code besides the constant raise ValueError.
120 >>> const_eval("[1,2, (3,4), {'foo':'bar'}]")
121 [1, 2, (3, 4), {'foo': 'bar'}]
122 >>> const_eval("1+2")
123 Traceback (most recent call last):
125 ValueError: opcode BINARY_ADD not allowed
127 c = test_expr(expr, _CONST_OPCODES)
131 """expr_eval(expression) -> value
133 Restricted Python expression evaluation
135 Evaluates a string that contains an expression that only
136 uses Python constants. This can be used to e.g. evaluate
137 a numerical expression from an untrusted source.
141 >>> expr_eval("[1,2]*2")
143 >>> expr_eval("__import__('sys').modules")
144 Traceback (most recent call last):
146 ValueError: opcode LOAD_NAME not allowed
148 c = test_expr(expr, _EXPR_OPCODES)
152 # Port of Python 2.6's ast.literal_eval for use under Python 2.5
153 SAFE_CONSTANTS = {'None': None, 'True': True, 'False': False}
156 # first, try importing directly
157 from ast import literal_eval
162 if isinstance(node, ast.Str):
164 elif isinstance(node, ast.Num):
166 elif isinstance(node, ast.Tuple):
167 return tuple(map(_convert, node.elts))
168 elif isinstance(node, ast.List):
169 return list(map(_convert, node.elts))
170 elif isinstance(node, ast.Dict):
171 return dict((_convert(k), _convert(v)) for k, v
172 in zip(node.keys, node.values))
173 elif isinstance(node, ast.Name):
174 if node.id in SAFE_CONSTANTS:
175 return SAFE_CONSTANTS[node.id]
176 raise ValueError('malformed or disallowed expression')
178 def parse(expr, filename='<unknown>', mode='eval'):
179 """parse(source[, filename], mode]] -> code object
180 Parse an expression into an AST node.
181 Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST).
183 return compile(expr, filename, mode, ast.PyCF_ONLY_AST)
185 def literal_eval(node_or_string):
186 """literal_eval(expression) -> value
187 Safely evaluate an expression node or a string containing a Python
188 expression. The string or node provided may only consist of the
189 following Python literal structures: strings, numbers, tuples,
190 lists, dicts, booleans, and None.
192 >>> literal_eval('[1,True,"spam"]')
195 >>> literal_eval('1+3')
196 Traceback (most recent call last):
198 ValueError: malformed or disallowed expression
200 if isinstance(node_or_string, basestring):
201 node_or_string = parse(node_or_string)
202 if isinstance(node_or_string, ast.Expression):
203 node_or_string = node_or_string.body
204 return _convert(node_or_string)
208 def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False):
209 """safe_eval(expression[, globals[, locals[, mode[, nocopy]]]]) -> result
211 System-restricted Python expression evaluation
213 Evaluates a string that contains an expression that mostly
214 uses Python constants, arithmetic expressions and the
215 objects directly provided in context.
217 This can be used to e.g. evaluate
218 an OpenERP domain expression from an untrusted source.
220 Throws TypeError, SyntaxError or ValueError (not allowed) accordingly.
222 >>> safe_eval("__import__('sys').modules")
223 Traceback (most recent call last):
225 ValueError: opcode LOAD_NAME not allowed
228 if isinstance(expr, CodeType):
229 raise ValueError("safe_eval does not allow direct evaluation of code objects.")
231 if '__subclasses__' in expr:
232 raise ValueError('expression not allowed (__subclasses__)')
234 if globals_dict is None:
237 # prevent altering the globals/locals from within the sandbox
240 # isinstance() does not work below, we want *exactly* the dict class
241 if (globals_dict is not None and type(globals_dict) is not dict) \
242 or (locals_dict is not None and type(locals_dict) is not dict):
243 logging.getLogger('safe_eval').warning('Looks like you are trying to pass a dynamic environment,"\
244 "you should probably pass nocopy=True to safe_eval()')
246 globals_dict = dict(globals_dict)
247 if locals_dict is not None:
248 locals_dict = dict(locals_dict)
271 return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict)
273 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: