212 lines
6.6 KiB
Python
212 lines
6.6 KiB
Python
import ast
|
|
import warnings
|
|
from collections import namedtuple
|
|
|
|
from RestrictedPython._compat import IS_CPYTHON
|
|
from RestrictedPython.transformer import RestrictingNodeTransformer
|
|
|
|
|
|
CompileResult = namedtuple(
|
|
'CompileResult', 'code, errors, warnings, used_names')
|
|
syntax_error_template = (
|
|
'Line {lineno}: {type}: {msg} at statement: {statement!r}')
|
|
|
|
NOT_CPYTHON_WARNING = (
|
|
'RestrictedPython is only supported on CPython: use on other Python '
|
|
'implementations may create security issues.'
|
|
)
|
|
|
|
|
|
def _compile_restricted_mode(
|
|
source,
|
|
filename='<string>',
|
|
mode="exec",
|
|
flags=0,
|
|
dont_inherit=False,
|
|
policy=RestrictingNodeTransformer):
|
|
|
|
if not IS_CPYTHON:
|
|
warnings.warn_explicit(
|
|
NOT_CPYTHON_WARNING, RuntimeWarning, 'RestrictedPython', 0)
|
|
|
|
byte_code = None
|
|
collected_errors = []
|
|
collected_warnings = []
|
|
used_names = {}
|
|
if policy is None:
|
|
# Unrestricted Source Checks
|
|
byte_code = compile(source, filename, mode=mode, flags=flags,
|
|
dont_inherit=dont_inherit)
|
|
elif issubclass(policy, RestrictingNodeTransformer):
|
|
c_ast = None
|
|
allowed_source_types = [str, ast.Module]
|
|
if not issubclass(type(source), tuple(allowed_source_types)):
|
|
raise TypeError('Not allowed source type: '
|
|
'"{0.__class__.__name__}".'.format(source))
|
|
c_ast = None
|
|
# workaround for pypy issue https://bitbucket.org/pypy/pypy/issues/2552
|
|
if isinstance(source, ast.Module):
|
|
c_ast = source
|
|
else:
|
|
try:
|
|
c_ast = ast.parse(source, filename, mode)
|
|
except (TypeError, ValueError) as e:
|
|
collected_errors.append(str(e))
|
|
except SyntaxError as v:
|
|
collected_errors.append(syntax_error_template.format(
|
|
lineno=v.lineno,
|
|
type=v.__class__.__name__,
|
|
msg=v.msg,
|
|
statement=v.text.strip() if v.text else None
|
|
))
|
|
if c_ast:
|
|
policy_instance = policy(
|
|
collected_errors, collected_warnings, used_names)
|
|
policy_instance.visit(c_ast)
|
|
if not collected_errors:
|
|
byte_code = compile(c_ast, filename, mode=mode # ,
|
|
# flags=flags,
|
|
# dont_inherit=dont_inherit
|
|
)
|
|
else:
|
|
raise TypeError('Unallowed policy provided for RestrictedPython')
|
|
return CompileResult(
|
|
byte_code,
|
|
tuple(collected_errors),
|
|
collected_warnings,
|
|
used_names)
|
|
|
|
|
|
def compile_restricted_exec(
|
|
source,
|
|
filename='<string>',
|
|
flags=0,
|
|
dont_inherit=False,
|
|
policy=RestrictingNodeTransformer):
|
|
"""Compile restricted for the mode `exec`."""
|
|
return _compile_restricted_mode(
|
|
source,
|
|
filename=filename,
|
|
mode='exec',
|
|
flags=flags,
|
|
dont_inherit=dont_inherit,
|
|
policy=policy)
|
|
|
|
|
|
def compile_restricted_eval(
|
|
source,
|
|
filename='<string>',
|
|
flags=0,
|
|
dont_inherit=False,
|
|
policy=RestrictingNodeTransformer):
|
|
"""Compile restricted for the mode `eval`."""
|
|
return _compile_restricted_mode(
|
|
source,
|
|
filename=filename,
|
|
mode='eval',
|
|
flags=flags,
|
|
dont_inherit=dont_inherit,
|
|
policy=policy)
|
|
|
|
|
|
def compile_restricted_single(
|
|
source,
|
|
filename='<string>',
|
|
flags=0,
|
|
dont_inherit=False,
|
|
policy=RestrictingNodeTransformer):
|
|
"""Compile restricted for the mode `single`."""
|
|
return _compile_restricted_mode(
|
|
source,
|
|
filename=filename,
|
|
mode='single',
|
|
flags=flags,
|
|
dont_inherit=dont_inherit,
|
|
policy=policy)
|
|
|
|
|
|
def compile_restricted_function(
|
|
p, # parameters
|
|
body,
|
|
name,
|
|
filename='<string>',
|
|
globalize=None, # List of globals (e.g. ['here', 'context', ...])
|
|
flags=0,
|
|
dont_inherit=False,
|
|
policy=RestrictingNodeTransformer):
|
|
"""Compile a restricted code object for a function.
|
|
|
|
Documentation see:
|
|
http://restrictedpython.readthedocs.io/en/latest/usage/index.html#RestrictedPython.compile_restricted_function
|
|
"""
|
|
# Parse the parameters and body, then combine them.
|
|
try:
|
|
body_ast = ast.parse(body, '<func code>', 'exec')
|
|
except SyntaxError as v:
|
|
error = syntax_error_template.format(
|
|
lineno=v.lineno,
|
|
type=v.__class__.__name__,
|
|
msg=v.msg,
|
|
statement=v.text.strip() if v.text else None)
|
|
return CompileResult(
|
|
code=None, errors=(error,), warnings=(), used_names=())
|
|
|
|
# The compiled code is actually executed inside a function
|
|
# (that is called when the code is called) so reading and assigning to a
|
|
# global variable like this`printed += 'foo'` would throw an
|
|
# UnboundLocalError.
|
|
# We don't want the user to need to understand this.
|
|
if globalize:
|
|
body_ast.body.insert(0, ast.Global(globalize))
|
|
wrapper_ast = ast.parse('def masked_function_name(%s): pass' % p,
|
|
'<func wrapper>', 'exec')
|
|
# In case the name you chose for your generated function is not a
|
|
# valid python identifier we set it after the fact
|
|
function_ast = wrapper_ast.body[0]
|
|
assert isinstance(function_ast, ast.FunctionDef)
|
|
function_ast.name = name
|
|
|
|
wrapper_ast.body[0].body = body_ast.body
|
|
wrapper_ast = ast.fix_missing_locations(wrapper_ast)
|
|
|
|
result = _compile_restricted_mode(
|
|
wrapper_ast,
|
|
filename=filename,
|
|
mode='exec',
|
|
flags=flags,
|
|
dont_inherit=dont_inherit,
|
|
policy=policy)
|
|
|
|
return result
|
|
|
|
|
|
def compile_restricted(
|
|
source,
|
|
filename='<unknown>',
|
|
mode='exec',
|
|
flags=0,
|
|
dont_inherit=False,
|
|
policy=RestrictingNodeTransformer):
|
|
"""Replacement for the built-in compile() function.
|
|
|
|
policy ... `ast.NodeTransformer` class defining the restrictions.
|
|
|
|
"""
|
|
if mode in ['exec', 'eval', 'single', 'function']:
|
|
result = _compile_restricted_mode(
|
|
source,
|
|
filename=filename,
|
|
mode=mode,
|
|
flags=flags,
|
|
dont_inherit=dont_inherit,
|
|
policy=policy)
|
|
else:
|
|
raise TypeError('unknown mode %s', mode)
|
|
for warning in result.warnings:
|
|
warnings.warn(
|
|
warning,
|
|
SyntaxWarning
|
|
)
|
|
if result.errors:
|
|
raise SyntaxError(result.errors)
|
|
return result.code
|