[IMP] work-in-progress: initial version of safe_eval implementation -- still need fixing
bzr revid: odo@openerp.com-20100503234953-qgbk64bmir95jvzd
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -75,6 +75,7 @@
|
||||
],
|
||||
'test': [
|
||||
'test/base_test.xml',
|
||||
#'test/base_test.yml'
|
||||
'test/test_context.xml',
|
||||
'bug_lp541545.xml',
|
||||
],
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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"
|
||||
|
||||
+5
-5
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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('\[\[(.+?)\]\]')
|
||||
|
||||
|
||||
@@ -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:
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
||||
@@ -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):
|
||||
|
||||
+57
-62
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user