summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier Dony <odo@openerp.com>2010-05-04 00:49:53 +0100
committerOlivier Dony <odo@openerp.com>2010-05-04 00:49:53 +0100
commitb020dc836345cadebb2e97ed883cb129f798e386 (patch)
treeb7697286de60bbb52243166d0b1ddaf9a7671baa
parentb220dc04ddbabb8d03b278d80640575bae61bf85 (diff)
[IMP] work-in-progress: initial version of safe_eval implementation -- still need fixing
bzr revid: odo@openerp.com-20100503234953-qgbk64bmir95jvzd
-rw-r--r--bin/addons/__init__.py3
-rw-r--r--bin/addons/base/__openerp__.py1
-rw-r--r--bin/addons/base/ir/ir_actions.py3
-rw-r--r--bin/addons/base/ir/ir_cron.py1
-rw-r--r--bin/addons/base/ir/ir_report_custom.py1
-rw-r--r--bin/addons/base/ir/ir_rule.py1
-rw-r--r--bin/addons/base/ir/ir_ui_view.py1
-rw-r--r--bin/addons/base/res/res_company.py1
-rw-r--r--bin/addons/base/res/res_lang.py1
-rw-r--r--bin/osv/orm.py10
-rw-r--r--bin/report/custom.py1
-rw-r--r--bin/report/print_xml.py3
-rw-r--r--bin/report/printscreen/ps_list.py1
-rw-r--r--bin/report/render/rml2pdf/trml2pdf.py1
-rw-r--r--bin/report/render/rml2pdf/utils.py1
-rw-r--r--bin/report/render/rml2txt/utils.py3
-rw-r--r--bin/tools/convert.py1
-rw-r--r--bin/tools/safe_eval.py119
-rw-r--r--bin/tools/yaml_import.py2
-rw-r--r--bin/workflow/wkf_expr.py1
20 files changed, 85 insertions, 71 deletions
diff --git a/bin/addons/__init__.py b/bin/addons/__init__.py
index ac8e951e2a3..bf4a0c78c18 100644
--- a/bin/addons/__init__.py
+++ b/bin/addons/__init__.py
@@ -28,6 +28,7 @@ import zipimport
import osv
import tools
import tools.osutil
+from tools.safe_eval import safe_eval as eval
import pooler
@@ -422,6 +423,8 @@ def register_class(m):
msg = "Couldn't load %smodule %s" % (mt, m)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, msg)
logger.notifyChannel('init', netsvc.LOG_CRITICAL, e)
+ import traceback
+ traceback.print_exc()
global loaded
if m in loaded:
diff --git a/bin/addons/base/__openerp__.py b/bin/addons/base/__openerp__.py
index 2308ecfccc8..26b2869758e 100644
--- a/bin/addons/base/__openerp__.py
+++ b/bin/addons/base/__openerp__.py
@@ -75,6 +75,7 @@
],
'test': [
'test/base_test.xml',
+ #'test/base_test.yml'
'test/test_context.xml',
'bug_lp541545.xml',
],
diff --git a/bin/addons/base/ir/ir_actions.py b/bin/addons/base/ir/ir_actions.py
index 6c51ab2f771..c3e9945df96 100644
--- a/bin/addons/base/ir/ir_actions.py
+++ b/bin/addons/base/ir/ir_actions.py
@@ -20,6 +20,7 @@
##############################################################################
from osv import fields,osv
+from tools.safe_eval import safe_eval as eval
import tools
import time
from tools.config import config
@@ -564,7 +565,7 @@ class actions_server(osv.osv):
'object':obj,
'obj': obj,
}
- exec action.code in localdict
+ eval(action.code, localdict, "exec")
if 'action' in localdict:
return localdict['action']
else:
diff --git a/bin/addons/base/ir/ir_cron.py b/bin/addons/base/ir/ir_cron.py
index 66ead94be48..905b2dc1778 100644
--- a/bin/addons/base/ir/ir_cron.py
+++ b/bin/addons/base/ir/ir_cron.py
@@ -25,6 +25,7 @@ from datetime import datetime
from dateutil.relativedelta import relativedelta
import netsvc
import tools
+from tools.safe_eval import safe_eval as eval
import pooler
from osv import fields, osv
diff --git a/bin/addons/base/ir/ir_report_custom.py b/bin/addons/base/ir/ir_report_custom.py
index 3b20d193d8b..036469fb417 100644
--- a/bin/addons/base/ir/ir_report_custom.py
+++ b/bin/addons/base/ir/ir_report_custom.py
@@ -24,6 +24,7 @@ from osv.orm import browse_null
import ir
import report.custom
from tools.translate import _
+from tools.safe_eval import safe_eval as eval
import netsvc
class report_custom(osv.osv):
diff --git a/bin/addons/base/ir/ir_rule.py b/bin/addons/base/ir/ir_rule.py
index de465fa8025..ba508b48920 100644
--- a/bin/addons/base/ir/ir_rule.py
+++ b/bin/addons/base/ir/ir_rule.py
@@ -22,6 +22,7 @@
from osv import fields,osv
import time
import tools
+from tools.safe_eval import safe_eval as eval
class ir_rule(osv.osv):
_name = 'ir.rule'
diff --git a/bin/addons/base/ir/ir_ui_view.py b/bin/addons/base/ir/ir_ui_view.py
index f1a50143ef3..bc395325840 100644
--- a/bin/addons/base/ir/ir_ui_view.py
+++ b/bin/addons/base/ir/ir_ui_view.py
@@ -22,6 +22,7 @@
from osv import fields,osv
from lxml import etree
from tools import graph
+from tools.safe_eval import safe_eval as eval
import tools
import netsvc
import os
diff --git a/bin/addons/base/res/res_company.py b/bin/addons/base/res/res_company.py
index 7d8a9cda8f3..b14c5b403c1 100644
--- a/bin/addons/base/res/res_company.py
+++ b/bin/addons/base/res/res_company.py
@@ -24,6 +24,7 @@ from osv import fields
import os
import tools
from tools.translate import _
+from tools.safe_eval import safe_eval as eval
class multi_company_default(osv.osv):
"""
diff --git a/bin/addons/base/res/res_lang.py b/bin/addons/base/res/res_lang.py
index 66c1463eba9..13c4a33aa9c 100644
--- a/bin/addons/base/res/res_lang.py
+++ b/bin/addons/base/res/res_lang.py
@@ -22,6 +22,7 @@
from osv import fields, osv
from locale import localeconv
import tools
+from tools.safe_eval import safe_eval as eval
class lang(osv.osv):
_name = "res.lang"
diff --git a/bin/osv/orm.py b/bin/osv/orm.py
index d6e0e6b1395..40104e6570c 100644
--- a/bin/osv/orm.py
+++ b/bin/osv/orm.py
@@ -56,7 +56,7 @@ from tools.translate import _
import fields
import tools
-
+from tools.safe_eval import safe_eval as eval
regex_order = re.compile('^(([a-z0-9_]+|"[a-z0-9_]+")( *desc| *asc)?( *, *|))+$', re.I)
@@ -353,8 +353,8 @@ def get_pg_type(f):
f_type = ('int4', 'INTEGER')
else:
f_type = ('varchar', 'VARCHAR(%d)' % f_size)
- elif isinstance(f, fields.function) and eval('fields.'+(f._type)) in type_dict:
- t = eval('fields.'+(f._type))
+ elif isinstance(f, fields.function) and eval('fields.'+(f._type),globals()) in type_dict:
+ t = eval('fields.'+(f._type), globals())
f_type = (type_dict[t], type_dict[t])
elif isinstance(f, fields.function) and f._type == 'float':
if f.digits:
@@ -1951,9 +1951,9 @@ class orm_memory(orm_template):
f = True
for arg in result:
if arg[1] =='=':
- val =eval('data[arg[0]]'+'==' +' arg[2]')
+ val =eval('data[arg[0]]'+'==' +' arg[2]', locals())
elif arg[1] in ['<','>','in','not in','<=','>=','<>']:
- val =eval('data[arg[0]]'+arg[1] +' arg[2]')
+ val =eval('data[arg[0]]'+arg[1] +' arg[2]', locals())
elif arg[1] in ['ilike']:
if str(data[arg[0]]).find(str(arg[2]))!=-1:
val= True
diff --git a/bin/report/custom.py b/bin/report/custom.py
index d4309b31ab9..b53be34cfdb 100644
--- a/bin/report/custom.py
+++ b/bin/report/custom.py
@@ -24,6 +24,7 @@ import time
import netsvc
import tools
+from tools.safe_eval import safe_eval as eval
import print_xml
import render
from interface import report_int
diff --git a/bin/report/print_xml.py b/bin/report/print_xml.py
index 2c24d3766f3..f349e10b1ba 100644
--- a/bin/report/print_xml.py
+++ b/bin/report/print_xml.py
@@ -23,6 +23,7 @@ import os,types
from lxml import etree
import netsvc
import tools
+from tools.safe_eval import safe_eval
import print_fnc
import copy
from osv.orm import browse_null, browse_record
@@ -111,7 +112,7 @@ class document(object):
#Pinky: Why not this ? eval(expr, browser) ?
# name = browser.name
# data_dict = browser._data[self.get_value(browser, 'id')]
- return eval(expr, {}, {'obj': record})
+ return safe_eval(expr, {}, {'obj': record})
def parse_node(self, node, parent, browser, datas=None):
attrs = self.node_attrs_get(node)
diff --git a/bin/report/printscreen/ps_list.py b/bin/report/printscreen/ps_list.py
index 0dea67382fe..4eba7372f2d 100644
--- a/bin/report/printscreen/ps_list.py
+++ b/bin/report/printscreen/ps_list.py
@@ -22,6 +22,7 @@
from report.interface import report_int
import pooler
import tools
+from tools.safe_eval import safe_eval as eval
from lxml import etree
from report import render
import locale
diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py
index a5912cd7a13..2db5f78ab5c 100644
--- a/bin/report/render/rml2pdf/trml2pdf.py
+++ b/bin/report/render/rml2pdf/trml2pdf.py
@@ -34,6 +34,7 @@ import os
from lxml import etree
import base64
from reportlab.platypus.doctemplate import ActionFlowable
+from tools.safe_eval import safe_eval as eval
encoding = 'utf-8'
diff --git a/bin/report/render/rml2pdf/utils.py b/bin/report/render/rml2pdf/utils.py
index ac4370ab204..ca51f136b6d 100644
--- a/bin/report/render/rml2pdf/utils.py
+++ b/bin/report/render/rml2pdf/utils.py
@@ -42,6 +42,7 @@ from lxml import etree
import copy
import locale
import traceback, sys
+from tools.safe_eval import safe_eval as eval
_regex = re.compile('\[\[(.+?)\]\]')
diff --git a/bin/report/render/rml2txt/utils.py b/bin/report/render/rml2txt/utils.py
index dc7651ea2ad..abbf2dc8426 100644
--- a/bin/report/render/rml2txt/utils.py
+++ b/bin/report/render/rml2txt/utils.py
@@ -23,6 +23,7 @@ import re
import reportlab
import reportlab.lib.units
from lxml import etree
+from tools.safe_eval import safe_eval as eval
_regex = re.compile('\[\[(.+?)\]\]')
@@ -128,4 +129,4 @@ def attr_get(node, attrs, dict={}):
res[key] = unit_get(node.get(key))
return res
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/bin/tools/convert.py b/bin/tools/convert.py
index a7e002e1a61..5fb20530408 100644
--- a/bin/tools/convert.py
+++ b/bin/tools/convert.py
@@ -35,6 +35,7 @@ import netsvc
import osv
import pooler
from config import config
+from tools.safe_eval import safe_eval as eval
from yaml_import import convert_yaml_import
class ConvertError(Exception):
diff --git a/bin/tools/safe_eval.py b/bin/tools/safe_eval.py
index 5eeb6258dc0..f1a2ca1b5e7 100644
--- a/bin/tools/safe_eval.py
+++ b/bin/tools/safe_eval.py
@@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
##############################################################################
-#
# Copyright (C) 2004-2010 OpenERP s.a. (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
@@ -14,15 +13,15 @@
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
"""
safe_eval module - methods intended to provide more restricted alternatives to
evaluate simple and/or untrusted code.
-
-Methods in this module are typically used as alternatives to eval() to parse
+
+Methods in this module are typically used as alternatives to eval() to parse
OpenERP domain strings, conditions and expressions, mostly based on locals
condition/math builtins.
"""
@@ -30,17 +29,19 @@ condition/math builtins.
# Module partially ripped from/inspired by several different sources:
# - http://code.activestate.com/recipes/286134/
# - safe_eval in lp:~xrg/openobject-server/optimize-5.0
-# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
+# - safe_eval in tryton http://hg.tryton.org/hgwebdir.cgi/trytond/rev/bbb5f73319ad
# - python 2.6's ast.literal_eval
from opcode import HAVE_ARGUMENT, opmap, opname
-_CONST_CODES = set(opmap[x] for x in [
+__all__ = ['test_expr', 'literal_eval', 'safe_eval', 'const_eval', 'ext_eval' ]
+
+_CONST_OPCODES = set(opmap[x] for x in [
'POP_TOP', 'ROT_TWO', 'ROT_THREE', 'ROT_FOUR', 'DUP_TOP',
'BUILD_LIST', 'BUILD_MAP', 'BUILD_TUPLE',
'LOAD_CONST', 'RETURN_VALUE', 'STORE_SUBSCR'] if x in opmap)
-_EXPR_CODES = _CONST_CODES.union(set(opmap[x] for x in [
+_EXPR_OPCODES = _CONST_OPCODES.union(set(opmap[x] for x in [
'UNARY_POSITIVE', 'UNARY_NEGATIVE', 'UNARY_NOT',
'UNARY_INVERT', 'BINARY_POWER', 'BINARY_MULTIPLY',
'BINARY_DIVIDE', 'BINARY_FLOOR_DIVIDE', 'BINARY_TRUE_DIVIDE',
@@ -48,10 +49,10 @@ _EXPR_CODES = _CONST_CODES.union(set(opmap[x] for x in [
'BINARY_LSHIFT', 'BINARY_RSHIFT', 'BINARY_AND', 'BINARY_XOR',
'BINARY_OR'] if x in opmap))
-_SAFE_CODES = _EXPR_CODES.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',
+ 'DELETE_NAME', 'JUMP_IF_TRUE', 'JUMP_IF_FALSE',
] if x in opmap))
@@ -66,25 +67,25 @@ def _get_opcodes(codeobj):
"""
i = 0
opcodes = []
- s = codeobj.co_code
- while i < len(s):
- code = ord(s[i])
+ byte_codes = codeobj.co_code
+ while i < len(byte_codes):
+ code = ord(byte_codes[i])
opcodes.append(code)
if code >= HAVE_ARGUMENT:
i += 3
else:
i += 1
- return opcodes
+ return opcodes
-def test_expr(expr, allowed_codes):
- """test_expr(expression) -> code_object
+def test_expr(expr, allowed_codes, mode="eval"):
+ """test_expr(expression, allowed_codes[, mode]) -> code_object
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.
"""
try:
- code_obj = compile(expr, "", "eval")
+ code_obj = compile(expr, "", mode)
except:
raise ValueError("%s is not a valid expression" % expr)
for code in _get_opcodes(code_obj):
@@ -111,7 +112,7 @@ def const_eval(expr):
...
ValueError: opcode BINARY_ADD not allowed
"""
- c = test_expr(expr, _CONST_CODES)
+ c = test_expr(expr, _CONST_OPCODES)
return eval(c)
def expr_eval(expr):
@@ -120,7 +121,7 @@ def expr_eval(expr):
Restricted Python expression evaluation
Evaluates a string that contains an expression that only
- uses Python constants. This can be used to e.g. evaluate
+ uses Python constants. This can be used to e.g. evaluate
a numerical expression from an untrusted source.
>>> expr_eval("1+2")
@@ -132,7 +133,7 @@ def expr_eval(expr):
...
ValueError: opcode LOAD_NAME not allowed
"""
- c = test_expr(expr, _EXPR_CODES)
+ c = test_expr(expr, _EXPR_OPCODES)
return eval(c)
@@ -143,31 +144,31 @@ try:
# first, try importing directly
from ast import literal_eval
except ImportError:
- from _ast import *
-
+ import _ast as ast
+
def _convert(node):
- if isinstance(node, Str):
+ if isinstance(node, ast.Str):
return node.s
- elif isinstance(node, Num):
+ elif isinstance(node, ast.Num):
return node.n
- elif isinstance(node, Tuple):
+ elif isinstance(node, ast.Tuple):
return tuple(map(_convert, node.elts))
- elif isinstance(node, List):
+ elif isinstance(node, ast.List):
return list(map(_convert, node.elts))
- elif isinstance(node, Dict):
+ elif isinstance(node, ast.Dict):
return dict((_convert(k), _convert(v)) for k, v
in zip(node.keys, node.values))
- elif isinstance(node, Name):
+ elif isinstance(node, ast.Name):
if node.id in SAFE_CONSTANTS:
return SAFE_CONSTANTS[node.id]
raise ValueError('malformed or disallowed expression')
-
+
def parse(expr, filename='<unknown>', mode='eval'):
"""parse(source[, filename], mode]] -> code object
Parse an expression into an AST node.
Equivalent to compile(expr, filename, mode, PyCF_ONLY_AST).
"""
- return compile(expr, filename, mode, PyCF_ONLY_AST)
+ return compile(expr, filename, mode, ast.PyCF_ONLY_AST)
def literal_eval(node_or_string):
"""literal_eval(expression) -> value
@@ -175,10 +176,10 @@ except ImportError:
expression. The string or node provided may only consist of the
following Python literal structures: strings, numbers, tuples,
lists, dicts, booleans, and None.
-
+
>>> literal_eval('[1,True,"spam"]')
[1, True, 'spam']
-
+
>>> literal_eval('1+3')
Traceback (most recent call last):
...
@@ -186,23 +187,23 @@ except ImportError:
"""
if isinstance(node_or_string, basestring):
node_or_string = parse(node_or_string)
- if isinstance(node_or_string, Expression):
+ if isinstance(node_or_string, ast.Expression):
node_or_string = node_or_string.body
return _convert(node_or_string)
-def safe_eval(expr, context = None):
- """safe_eval(expression, context) -> value
+def safe_eval(expr, globals_dict=None, locals_dict=None, mode="eval"):
+ """safe_eval(expression[, globals[, locals[, mode]]]) -> value
System-restricted Python expression evaluation
Evaluates a string that contains an expression that mostly
uses Python constants, arithmetic expressions and the
- use of the objects provided in context.
-
- This can be used to e.g. evaluate
- an OpenERP domain expression from an untrusted expr.
+ objects directly provided in context.
+
+ This can be used to e.g. evaluate
+ an OpenERP domain expression from an untrusted source.
>>> safe_eval("__import__('sys').modules")
Traceback (most recent call last):
@@ -212,30 +213,24 @@ def safe_eval(expr, context = None):
"""
if '__subclasses__' in expr:
raise ValueError('expression not allowed (__subclasses__)')
- code_obj = compile(expr, '', 'eval')
- byte_codes = code_obj.co_code
- i = 0
- while i < len(byte_codes):
- op_code = ord(byte_codes[i])
- if op_code not in _SAFE_CODES:
- raise ValueError('opcode %byte_codes not allowed' % dis.opname[op_code])
- if op_code >= HAVE_ARGUMENT:
- i += 3
- else:
- i += 1
- return eval(code_obj, {'__builtins__': {
- 'True': True,
- 'False': False,
- 'None': None,
- 'str': str,
- 'globals': locals,
- 'locals': locals,
- 'bool': bool,
- 'dict': dict,
- 'list': list,
- 'tuple': tuple,
- }
- }, context)
+
+ if globals_dict is None:
+ globals_dict = {}
+ globals_dict.update(
+ __builtins__ = {
+ 'True': True,
+ 'False': False,
+ 'None': None,
+ 'str': str,
+ 'globals': locals,
+ 'locals': locals,
+ 'bool': bool,
+ 'dict': dict,
+ 'list': list,
+ 'tuple': tuple,
+ }
+ )
+ return eval(test_expr(expr,_SAFE_OPCODES, mode=mode), globals_dict, locals_dict)
diff --git a/bin/tools/yaml_import.py b/bin/tools/yaml_import.py
index df825f09cbb..e1dd94e63c0 100644
--- a/bin/tools/yaml_import.py
+++ b/bin/tools/yaml_import.py
@@ -7,8 +7,8 @@ import pooler
import netsvc
import misc
from config import config
+from tools.safe_eval import safe_eval as eval
import yaml_tag
-
import yaml
logger_channel = 'tests'
diff --git a/bin/workflow/wkf_expr.py b/bin/workflow/wkf_expr.py
index 98896df9324..6d3c0a19401 100644
--- a/bin/workflow/wkf_expr.py
+++ b/bin/workflow/wkf_expr.py
@@ -23,6 +23,7 @@ import sys
import netsvc
import osv as base
import pooler
+from tools.safe_eval import safe_eval as eval
class Env(dict):
def __init__(self, cr, uid, model, ids):