@@ -239,6 +239,7 @@ def assert_valid_codeobj(allowed_codes, code_obj, expr):
239
239
if isinstance (const , CodeType ):
240
240
assert_valid_codeobj (allowed_codes , const , 'lambda' )
241
241
242
+
242
243
def test_expr (expr , allowed_codes , mode = "eval" , filename = None ):
243
244
"""test_expr(expression, allowed_codes[, mode[, filename]]) -> code_object
244
245
@@ -344,18 +345,35 @@ def expr_eval(expr):
344
345
'zip' : zip ,
345
346
'Exception' : Exception ,
346
347
}
347
- def safe_eval (expr , globals_dict = None , locals_dict = None , mode = "eval" , nocopy = False , locals_builtins = False , filename = None ):
348
- """safe_eval(expression[, globals[, locals[, mode[, nocopy]]]]) -> result
349
348
350
- System-restricted Python expression evaluation
349
+
350
+ _BUBBLEUP_EXCEPTIONS = (
351
+ odoo .exceptions .UserError ,
352
+ odoo .exceptions .RedirectWarning ,
353
+ werkzeug .exceptions .HTTPException ,
354
+ OperationalError , # let auto-replay of serialized transactions work its magic
355
+ ZeroDivisionError ,
356
+ )
357
+
358
+
359
+ def safe_eval (expr , / , context = None , * , mode = "eval" , filename = None ):
360
+ """System-restricted Python expression evaluation
351
361
352
362
Evaluates a string that contains an expression that mostly
353
363
uses Python constants, arithmetic expressions and the
354
364
objects directly provided in context.
355
365
356
366
This can be used to e.g. evaluate
357
- an OpenERP domain expression from an untrusted source.
358
-
367
+ a domain expression from an untrusted source.
368
+
369
+ :param expr: The Python expression (or block, if ``mode='exec'``) to evaluate.
370
+ :type expr: string | bytes
371
+ :param context: Namespace available to the expression.
372
+ This dict will be mutated with any variables created during
373
+ evaluation
374
+ :type context: dict
375
+ :param mode: ``exec`` or ``eval``
376
+ :type mode: str
359
377
:param filename: optional pseudo-filename for the compiled expression,
360
378
displayed for example in traceback frames
361
379
:type filename: string
@@ -367,48 +385,29 @@ def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval", nocopy=Fal
367
385
if type (expr ) is CodeType :
368
386
raise TypeError ("safe_eval does not allow direct evaluation of code objects." )
369
387
370
- # prevent altering the globals/locals from within the sandbox
371
- # by taking a copy.
372
- if not nocopy :
373
- # isinstance() does not work below, we want *exactly* the dict class
374
- if (globals_dict is not None and type (globals_dict ) is not dict ) \
375
- or (locals_dict is not None and type (locals_dict ) is not dict ):
376
- _logger .warning (
377
- "Looks like you are trying to pass a dynamic environment, "
378
- "you should probably pass nocopy=True to safe_eval()." )
379
- if globals_dict is not None :
380
- globals_dict = dict (globals_dict )
381
- if locals_dict is not None :
382
- locals_dict = dict (locals_dict )
383
-
384
- check_values (globals_dict )
385
- check_values (locals_dict )
386
-
387
- if globals_dict is None :
388
- globals_dict = {}
389
-
390
- globals_dict ['__builtins__' ] = dict (_BUILTINS )
391
- if locals_builtins :
392
- if locals_dict is None :
393
- locals_dict = {}
394
- locals_dict .update (_BUILTINS )
388
+ assert context is None or type (context ) is dict , "Context must be a dict"
389
+
390
+ check_values (context )
391
+
392
+ globals_dict = dict (context or {}, __builtins__ = dict (_BUILTINS ))
393
+
395
394
c = test_expr (expr , _SAFE_OPCODES , mode = mode , filename = filename )
396
395
try :
397
- return unsafe_eval (c , globals_dict , locals_dict )
398
- except odoo .exceptions .UserError :
399
- raise
400
- except odoo .exceptions .RedirectWarning :
401
- raise
402
- except werkzeug .exceptions .HTTPException :
403
- raise
404
- except OperationalError :
405
- # Do not hide PostgreSQL low-level exceptions, to let the auto-replay
406
- # of serialized transactions work its magic
407
- raise
408
- except ZeroDivisionError :
396
+ # empty locals dict makes the eval behave like top-level code
397
+ return unsafe_eval (c , globals_dict , None )
398
+
399
+ except _BUBBLEUP_EXCEPTIONS :
409
400
raise
401
+
410
402
except Exception as e :
411
403
raise ValueError ('%r while evaluating\n %r' % (e , expr ))
404
+
405
+ finally :
406
+ if context is not None :
407
+ del globals_dict ['__builtins__' ]
408
+ context .update (globals_dict )
409
+
410
+
412
411
def test_python_expr (expr , mode = "eval" ):
413
412
try :
414
413
test_expr (expr , _SAFE_OPCODES , mode = mode )
0 commit comments