Repository navigation
Expand file tree
/
Copy pathEval.py
More file actions
127 lines (94 loc) · 3.75 KB
/
Copy pathEval.py
File metadata and controls
127 lines (94 loc) · 3.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
##############################################################################
#
# Copyright (c) 2002 Zope Foundation and Contributors.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""Restricted Python Expressions."""
import ast
import collections
import types
import typing
from RestrictedPython._types import cast_not_none
from RestrictedPython.compile import compile_restricted_eval
nltosp = str.maketrans('\r\n', ' ')
# No restrictions.
default_guarded_getattr = getattr
_T = typing.TypeVar('_T')
_TK = typing.TypeVar('_TK', contravariant=True)
_TV = typing.TypeVar('_TV', covariant=True)
class _GetItem(typing.Protocol[_TK, _TV]):
def __getitem__(self, key: _TK) -> _TV: ... # pragma: no cover
def default_guarded_getitem(ob: _GetItem[_TK, _TV], index: _TK) -> _TV:
# No restrictions.
return ob[index]
def default_guarded_getiter(ob: _T) -> _T:
# No restrictions.
return ob
class RestrictionCapableEval:
"""A base class for restricted code."""
globals: dict[str, typing.Any] = {'__builtins__': None}
# restricted
rcode: types.CodeType | None = None
# unrestricted
ucode: types.CodeType | None = None
# Names used by the expression
used: tuple[str, ...] | None = None
def __init__(self, expr: str):
"""Create a restricted expression
where:
expr -- a string containing the expression to be evaluated.
"""
expr = expr.strip()
self.__name__ = expr
expr = expr.translate(nltosp)
self.expr = expr
# Catch syntax errors.
self.prepUnrestrictedCode()
def prepRestrictedCode(self) -> None:
if self.rcode is None:
result = compile_restricted_eval(self.expr, '<string>')
if result.errors:
raise SyntaxError(result.errors[0])
self.used = tuple(result.used_names)
self.rcode = result.code
def prepUnrestrictedCode(self) -> None:
if self.ucode is None:
exp_node = ast.parse(
self.expr,
'<string>',
'eval')
co = compile(exp_node, '<string>', 'eval')
# Examine the ast to discover which names the expression needs.
if self.used is None:
used = set()
for node in ast.walk(exp_node):
if isinstance(node, ast.Name):
if isinstance(node.ctx, ast.Load):
used.add(node.id)
self.used = tuple(used)
self.ucode = co
def eval(self,
mapping: collections.abc.Mapping[str,
typing.Any]) -> typing.Any:
# This default implementation is probably not very useful. :-(
# This is meant to be overridden.
self.prepRestrictedCode()
global_scope = {
'_getattr_': default_guarded_getattr,
'_getitem_': default_guarded_getitem,
'_getiter_': default_guarded_getiter,
}
global_scope.update(self.globals)
for name in cast_not_none(self.used):
if (name not in global_scope) and (name in mapping):
global_scope[name] = mapping[name]
return eval(cast_not_none(self.rcode), global_scope)
def __call__(self, **kw: typing.Any) -> typing.Any:
return self.eval(kw)