[REVERT] r3591: causing problem to install some modules
[odoo/odoo.git] / bin / tools / safe_eval.py
index bd8e8ec..6c068d8 100644 (file)
@@ -34,11 +34,18 @@ condition/math builtins.
 
 from opcode import HAVE_ARGUMENT, opmap, opname
 from types import CodeType
+import logging
+import os
 
 __all__ = ['test_expr', 'literal_eval', 'safe_eval', 'const_eval', 'ext_eval' ]
 
+# The time module is usually already provided in the safe_eval environment
+# but some code, e.g. datetime.datetime.now() (Windows/Python 2.5.2, bug
+# lp:703841), does import time.
+_ALLOWED_MODULES = ['_strptime', 'time']
+
 _CONST_OPCODES = set(opmap[x] for x in [
-    'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP',
+    'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP','POP_BLOCK','SETUP_LOOP',
     'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
     'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR'] if x in opmap)
 
@@ -52,10 +59,15 @@ _EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
 
 _SAFE_OPCODES = _EXPR_OPCODES.union(set(opmap[x] for x in [
     'STORE_MAP', 'LOAD_NAME', 'CALL_FUNCTION', 'COMPARE_OP', 'LOAD_ATTR',
-    'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'JUMP_ABSOLUTE',
-    'DELETE_NAME', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE',
+    'STORE_NAME', 'GET_ITER', 'FOR_ITER', 'LIST_APPEND', 'DELETE_NAME',
+    'JUMP_FORWARD', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE', 'JUMP_ABSOLUTE',
+    'MAKE_FUNCTION', 'SLICE+0', 'SLICE+1', 'SLICE+2', 'SLICE+3',
+    # New in Python 2.7 - http://bugs.python.org/issue4715 :
+    'JUMP_IF_FALSE_OR_POP', 'JUMP_IF_TRUE_OR_POP', 'POP_JUMP_IF_FALSE',
+    'POP_JUMP_IF_TRUE'
     ] if x in opmap))
 
+_logger = logging.getLogger('safe_eval')
 
 def _get_opcodes(codeobj):
     """_get_opcodes(codeobj) -> [opcodes]
@@ -83,11 +95,19 @@ def test_expr(expr, allowed_codes, mode="eval"):
 
     Test that the expression contains only the allowed opcodes.
     If the expression is valid and contains only allowed codes,
-    return the compiled code object. Otherwise raise a ValueError.
+    return the compiled code object.
+    Otherwise raise a ValueError, a Syntax Error or TypeError accordingly.
     """
     try:
+        if mode == 'eval':
+            # eval() does not like leading/trailing whitespace
+            expr = expr.strip()
         code_obj = compile(expr, "", mode)
-    except:
+    except (SyntaxError, TypeError):
+        _logger.debug('Invalid eval expression', exc_info=True)
+        raise
+    except Exception:
+        _logger.debug('Disallowed or invalid eval expression', exc_info=True)
         raise ValueError("%s is not a valid expression" % expr)
     for code in _get_opcodes(code_obj):
         if code not in allowed_codes:
@@ -192,7 +212,10 @@ except ImportError:
             node_or_string = node_or_string.body
         return _convert(node_or_string)
 
-
+def _import(name, globals={}, locals={}, fromlist=[], level=-1):
+    if name in _ALLOWED_MODULES:
+        return __import__(name, globals, locals, level)
+    raise ImportError(name)
 
 def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=False):
     """safe_eval(expression[, globals[, locals[, mode[, nocopy]]]]) -> result
@@ -206,6 +229,8 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
     This can be used to e.g. evaluate
     an OpenERP domain expression from an untrusted source.
 
+    Throws TypeError, SyntaxError or ValueError (not allowed) accordingly.
+
     >>> safe_eval("__import__('sys').modules")
     Traceback (most recent call last):
     ...
@@ -236,6 +261,7 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
 
     globals_dict.update(
             __builtins__ = {
+                '__import__': _import,
                 'True': True,
                 'False': False,
                 'None': None,
@@ -246,9 +272,15 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
                 'dict': dict,
                 'list': list,
                 'tuple': tuple,
+                'map': map,
+                'abs': abs,
+                'reduce': reduce,
+                'filter': filter,
+                'round': round,
+                'len': len,
+                'set' : set
             }
     )
-
     return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict)
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: