1 # -*- coding: utf-8 -*-
2 ##############################################################################
3 # Copyright (C) 2004-2012 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
34 from opcode import HAVE_ARGUMENT, opmap, opname
35 from types import CodeType
38 from .misc import ustr
42 __all__ = ['test_expr', 'safe_eval', 'const_eval']
44 # The time module is usually already provided in the safe_eval environment
45 # but some code, e.g. datetime.datetime.now() (Windows/Python 2.5.2, bug
46 # lp:703841), does import time.
47 _ALLOWED_MODULES = ['_strptime', 'time']
49 _CONST_OPCODES = set(opmap[x] for x in [
50 'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP', 'DUP_TOPX',
51 'POP_BLOCK','SETUP_LOOP', 'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
52 'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR', 'STORE_MAP'] if x in opmap)
54 _EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
55 'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT',
56 'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY',
57 'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE',
58 'BINARY_MODULO', 'BINARY_ADD', 'BINARY_SUBTRACT', 'BINARY_SUBSCR',
59 'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR',
60 'BINARY_OR', 'INPLACE_ADD', 'INPLACE_SUBTRACT', 'INPLACE_MULTIPLY',
61 'INPLACE_DIVIDE', 'INPLACE_REMAINDER', 'INPLACE_POWER',
62 'INPLACE_LEFTSHIFT', 'INPLACE_RIGHTSHIFT', 'INPLACE_AND',
63 'INPLACE_XOR','INPLACE_OR'
66 _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
67 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR',
68 'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'DELETE_NAME',
69 'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE',
70 'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3', 'BREAK_LOOP',
71 'CONTINUE_LOOP', 'RAISE_VARARGS',
72 # New in Python 2.7 - http://bugs.python.org/issue4715 :
73 'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
74 'POP_JUMP_IF_TRUE', 'SETUP_EXCEPT', 'END_FINALLY'
77 _logger = logging.getLogger(__name__)
79 def _get_opcodes(codeobj):
80 """_get_opcodes(codeobj) -> [opcodes]
82 Extract the actual opcodes as a list from a code object
84 >>> c = compile("[1 + 2, (1,2)]", "", "eval")
86 [100, 100, 23, 100, 100, 102, 103, 83]
90 byte_codes = codeobj.co_code
91 while i < len(byte_codes):
92 code = ord(byte_codes[i])
94 if code >= HAVE_ARGUMENT:
100 def test_expr(expr, allowed_codes, mode="eval"):
101 """test_expr(expression, allowed_codes[, mode]) -> code_object
103 Test that the expression contains only the allowed opcodes.
104 If the expression is valid and contains only allowed codes,
105 return the compiled code object.
106 Otherwise raise a ValueError, a Syntax Error or TypeError accordingly.
110 # eval() does not like leading/trailing whitespace
112 code_obj = compile(expr, "", mode)
113 except (SyntaxError, TypeError):
117 exc_info = sys.exc_info()
118 raise ValueError, '"%s" while compiling\n%r' % (ustr(e), expr), exc_info[2]
119 for code in _get_opcodes(code_obj):
120 if code not in allowed_codes:
121 raise ValueError("opcode %s not allowed (%r)" % (opname[code], expr))
125 def const_eval(expr):
126 """const_eval(expression) -> value
128 Safe Python constant evaluation
130 Evaluates a string that contains an expression describing
131 a Python constant. Strings that are not valid Python expressions
132 or that contain other code besides the constant raise ValueError.
136 >>> const_eval("[1,2, (3,4), {'foo':'bar'}]")
137 [1, 2, (3, 4), {'foo': 'bar'}]
138 >>> const_eval("1+2")
139 Traceback (most recent call last):
141 ValueError: opcode BINARY_ADD not allowed
143 c = test_expr(expr, _CONST_OPCODES)
147 """expr_eval(expression) -> value
149 Restricted Python expression evaluation
151 Evaluates a string that contains an expression that only
152 uses Python constants. This can be used to e.g. evaluate
153 a numerical expression from an untrusted source.
157 >>> expr_eval("[1,2]*2")
159 >>> expr_eval("__import__('sys').modules")
160 Traceback (most recent call last):
162 ValueError: opcode LOAD_NAME not allowed
164 c = test_expr(expr, _EXPR_OPCODES)
167 def _import(name, globals=None, locals=None, fromlist=None, level=-1):
174 if name in _ALLOWED_MODULES:
175 return __import__(name, globals, locals, level)
176 raise ImportError(name)
178 def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False, locals_builtins=False):
179 """safe_eval(expression[, globals[, locals[, mode[, nocopy]]]]) -> result
181 System-restricted Python expression evaluation
183 Evaluates a string that contains an expression that mostly
184 uses Python constants, arithmetic expressions and the
185 objects directly provided in context.
187 This can be used to e.g. evaluate
188 an OpenERP domain expression from an untrusted source.
190 Throws TypeError, SyntaxError or ValueError (not allowed) accordingly.
192 >>> safe_eval("__import__('sys').modules")
193 Traceback (most recent call last):
195 ValueError: opcode LOAD_NAME not allowed
198 if isinstance(expr, CodeType):
199 raise ValueError("safe_eval does not allow direct evaluation of code objects.")
201 if '__subclasses__' in expr:
202 raise ValueError('expression not allowed (__subclasses__)')
204 if globals_dict is None:
207 # prevent altering the globals/locals from within the sandbox
210 # isinstance() does not work below, we want *exactly* the dict class
211 if (globals_dict is not None and type(globals_dict) is not dict) \
212 or (locals_dict is not None and type(locals_dict) is not dict):
214 "Looks like you are trying to pass a dynamic environment, "
215 "you should probably pass nocopy=True to safe_eval().")
217 globals_dict = dict(globals_dict)
218 if locals_dict is not None:
219 locals_dict = dict(locals_dict)
223 '__import__': _import,
250 if locals_dict is None:
252 locals_dict.update(globals_dict.get('__builtins__'))
253 c = test_expr(expr, _SAFE_OPCODES, mode=mode)
255 return eval(c, globals_dict, locals_dict)
256 except openerp.osv.orm.except_orm:
258 except openerp.exceptions.Warning:
260 except openerp.exceptions.RedirectWarning:
262 except openerp.exceptions.AccessDenied:
264 except openerp.exceptions.AccessError:
268 exc_info = sys.exc_info()
269 raise ValueError, '"%s" while evaluating\n%r' % (ustr(e), expr), exc_info[2]
271 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: