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
Memory