Spaces:
Running
Running
from RestrictedPython import compile_restricted | |
from RestrictedPython.PrintCollector import PrintCollector | |
from RestrictedPython.Guards import safe_globals, safe_builtins, guarded_iter_unpack_sequence | |
from RestrictedPython.Eval import default_guarded_getiter, default_guarded_getitem | |
from RestrictedPython.Utilities import utility_builtins | |
from RestrictedPython.Guards import guarded_unpack_sequence | |
from RestrictedPython.Limits import limited_range, limited_list, limited_tuple | |
from io import StringIO | |
import types | |
def eval_restricted_script(script): | |
# Set up print collector and output handling | |
all_prints = StringIO() | |
class CustomPrintCollector: | |
"""Collect printed text, accumulating in shared StringIO""" | |
def __init__(self, _getattr_=None): | |
self.txt = [] | |
self._getattr_ = _getattr_ | |
def write(self, text): | |
all_prints.write(text) | |
self.txt.append(text) | |
def __call__(self): | |
result = ''.join(self.txt) | |
return result | |
def _call_print(self, *objects, **kwargs): | |
if kwargs.get('file', None) is None: | |
kwargs['file'] = self | |
else: | |
self._getattr_(kwargs['file'], 'write') | |
print(*objects, **kwargs) | |
# Create the restricted builtins dictionary | |
restricted_builtins = dict(safe_builtins) | |
restricted_builtins.update(utility_builtins) # Add safe __import__ | |
restricted_builtins.update({ | |
# Print handling | |
'_print_': CustomPrintCollector, | |
'_getattr_': getattr, | |
'_getitem_': default_guarded_getitem, | |
'_getiter_': default_guarded_getiter, | |
'_iter_unpack_sequence_': guarded_iter_unpack_sequence, | |
'_unpack_sequence_': guarded_unpack_sequence, | |
'_inplacevar_': protected_inplacevar, | |
'_apply_': _apply, | |
'_write_': _default_write_, | |
# Define allowed imports | |
'__allowed_modules__': ['math', 'datetime'], | |
'__import__': __import__, | |
# Basic functions | |
'len': len, | |
'range': limited_range, | |
'enumerate': enumerate, | |
'zip': zip, | |
# Math operations | |
'sum': sum, | |
'max': max, | |
'min': min, | |
'abs': abs, | |
'round': round, | |
'pow': pow, | |
# Type conversions | |
'int': int, | |
'float': float, | |
'str': str, | |
'bool': bool, | |
'list': limited_list, | |
'tuple': limited_tuple, | |
'set': set, | |
'dict': dict, | |
'bytes': bytes, | |
'bytearray': bytearray, | |
# Sequence operations | |
'all': all, | |
'any': any, | |
'sorted': sorted, | |
'reversed': reversed, | |
# String operations | |
'chr': chr, | |
'ord': ord, | |
# Other safe operations | |
'isinstance': isinstance, | |
'issubclass': issubclass, | |
'hasattr': hasattr, | |
'callable': callable, | |
'format': format, | |
}) | |
# Create the restricted globals dictionary | |
restricted_globals = dict(safe_globals) | |
restricted_globals['__builtins__'] = restricted_builtins | |
try: | |
byte_code = compile_restricted(script, filename='<inline>', mode='exec') | |
exec(byte_code, restricted_globals) | |
return { | |
'prints': all_prints.getvalue(), | |
'success': True | |
} | |
except Exception as e: | |
return { | |
'error': str(e), | |
'success': False | |
} | |
def _default_write_(obj): | |
if isinstance(obj, types.ModuleType): | |
raise ValueError("Modules are not allowed in to be written to.") | |
return obj | |
""" | |
Borrowed implementation of _inplacevar_ from the Zope Foundations's AccessControl module | |
https://github.com/zopefoundation/AccessControl/blob/f9ae58816f0712eb6ea97459b4ccafbf4662d9db/src/AccessControl/ZopeGuards.py#L530 | |
""" | |
valid_inplace_types = (list, set) | |
inplace_slots = { | |
'+=': '__iadd__', | |
'-=': '__isub__', | |
'*=': '__imul__', | |
'/=': (1 / 2 == 0) and '__idiv__' or '__itruediv__', | |
'//=': '__ifloordiv__', | |
'%=': '__imod__', | |
'**=': '__ipow__', | |
'<<=': '__ilshift__', | |
'>>=': '__irshift__', | |
'&=': '__iand__', | |
'^=': '__ixor__', | |
'|=': '__ior__', | |
} | |
def __iadd__(x, y): | |
x += y | |
return x | |
def __isub__(x, y): | |
x -= y | |
return x | |
def __imul__(x, y): | |
x *= y | |
return x | |
def __idiv__(x, y): | |
x /= y | |
return x | |
def __ifloordiv__(x, y): | |
x //= y | |
return x | |
def __imod__(x, y): | |
x %= y | |
return x | |
def __ipow__(x, y): | |
x **= y | |
return x | |
def __ilshift__(x, y): | |
x <<= y | |
return x | |
def __irshift__(x, y): | |
x >>= y | |
return x | |
def __iand__(x, y): | |
x &= y | |
return x | |
def __ixor__(x, y): | |
x ^= y | |
return x | |
def __ior__(x, y): | |
x |= y | |
return x | |
inplace_ops = { | |
'+=': __iadd__, | |
'-=': __isub__, | |
'*=': __imul__, | |
'/=': __idiv__, | |
'//=': __ifloordiv__, | |
'%=': __imod__, | |
'**=': __ipow__, | |
'<<=': __ilshift__, | |
'>>=': __irshift__, | |
'&=': __iand__, | |
'^=': __ixor__, | |
'|=': __ior__, | |
} | |
def protected_inplacevar(op, var, expr): | |
"""Do an inplace operation | |
If the var has an inplace slot, then disallow the operation | |
unless the var an instance of ``valid_inplace_types``. | |
""" | |
if hasattr(var, inplace_slots[op]) and \ | |
not isinstance(var, valid_inplace_types): | |
try: | |
cls = var.__class__ | |
except AttributeError: | |
cls = type(var) | |
raise TypeError( | |
"Augmented assignment to %s objects is not allowed" | |
" in untrusted code" % cls.__name__) | |
return inplace_ops[op](var, expr) | |
def _apply(f, *a, **kw): | |
return f(*a, **kw) |