summaryrefslogtreecommitdiffstats
path: root/lib/kross/python/scripts/RestrictedPython/RCompile.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/kross/python/scripts/RestrictedPython/RCompile.py')
-rw-r--r--lib/kross/python/scripts/RestrictedPython/RCompile.py235
1 files changed, 235 insertions, 0 deletions
diff --git a/lib/kross/python/scripts/RestrictedPython/RCompile.py b/lib/kross/python/scripts/RestrictedPython/RCompile.py
new file mode 100644
index 00000000..0a538657
--- /dev/null
+++ b/lib/kross/python/scripts/RestrictedPython/RCompile.py
@@ -0,0 +1,235 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (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
+#
+##############################################################################
+"""Compiles restricted code using the compiler module from the
+Python standard library.
+"""
+
+__version__='$Revision: 1.6 $'[11:-2]
+
+from compiler import ast, parse, misc, syntax, pycodegen
+from compiler.pycodegen import AbstractCompileMode, Expression, \
+ Interactive, Module, ModuleCodeGenerator, FunctionCodeGenerator, findOp
+
+import MutatingWalker
+from RestrictionMutator import RestrictionMutator
+
+
+def niceParse(source, filename, mode):
+ try:
+ return parse(source, mode)
+ except:
+ # Try to make a clean error message using
+ # the builtin Python compiler.
+ try:
+ compile(source, filename, mode)
+ except SyntaxError:
+ raise
+ # Some other error occurred.
+ raise
+
+class RestrictedCompileMode(AbstractCompileMode):
+ """Abstract base class for hooking up custom CodeGenerator."""
+ # See concrete subclasses below.
+
+ def __init__(self, source, filename):
+ self.rm = RestrictionMutator()
+ AbstractCompileMode.__init__(self, source, filename)
+
+ def parse(self):
+ return niceParse(self.source, self.filename, self.mode)
+
+ def _get_tree(self):
+ tree = self.parse()
+ MutatingWalker.walk(tree, self.rm)
+ if self.rm.errors:
+ raise SyntaxError, self.rm.errors[0]
+ misc.set_filename(self.filename, tree)
+ syntax.check(tree)
+ return tree
+
+ def compile(self):
+ tree = self._get_tree()
+ gen = self.CodeGeneratorClass(tree)
+ self.code = gen.getCode()
+
+
+def compileAndTuplize(gen):
+ try:
+ gen.compile()
+ except SyntaxError, v:
+ return None, (str(v),), gen.rm.warnings, gen.rm.used_names
+ return gen.getCode(), (), gen.rm.warnings, gen.rm.used_names
+
+def compile_restricted_function(p, body, name, filename, globalize=None):
+ """Compiles a restricted code object for a function.
+
+ The function can be reconstituted using the 'new' module:
+
+ new.function(<code>, <globals>)
+
+ The globalize argument, if specified, is a list of variable names to be
+ treated as globals (code is generated as if each name in the list
+ appeared in a global statement at the top of the function).
+ """
+ gen = RFunction(p, body, name, filename, globalize)
+ return compileAndTuplize(gen)
+
+def compile_restricted_exec(s, filename='<string>'):
+ """Compiles a restricted code suite."""
+ gen = RModule(s, filename)
+ return compileAndTuplize(gen)
+
+def compile_restricted_eval(s, filename='<string>'):
+ """Compiles a restricted expression."""
+ gen = RExpression(s, filename)
+ return compileAndTuplize(gen)
+
+def compile_restricted(source, filename, mode):
+ """Replacement for the builtin compile() function."""
+ if mode == "single":
+ gen = RInteractive(source, filename)
+ elif mode == "exec":
+ gen = RModule(source, filename)
+ elif mode == "eval":
+ gen = RExpression(source, filename)
+ else:
+ raise ValueError("compile_restricted() 3rd arg must be 'exec' or "
+ "'eval' or 'single'")
+ gen.compile()
+ return gen.getCode()
+
+class RestrictedCodeGenerator:
+ """Mixin for CodeGenerator to replace UNPACK_SEQUENCE bytecodes.
+
+ The UNPACK_SEQUENCE opcode is not safe because it extracts
+ elements from a sequence without using a safe iterator or
+ making __getitem__ checks.
+
+ This code generator replaces use of UNPACK_SEQUENCE with calls to
+ a function that unpacks the sequence, performes the appropriate
+ security checks, and returns a simple list.
+ """
+
+ # Replace the standard code generator for assignments to tuples
+ # and lists.
+
+ def _gen_safe_unpack_sequence(self, num):
+ # We're at a place where UNPACK_SEQUENCE should be generated, to
+ # unpack num items. That's a security hole, since it exposes
+ # individual items from an arbitrary iterable. We don't remove
+ # the UNPACK_SEQUENCE, but instead insert a call to our _getiter_()
+ # wrapper first. That applies security checks to each item as
+ # it's delivered. codegen is (just) a bit messy because the
+ # iterable is already on the stack, so we have to do a stack swap
+ # to get things in the right order.
+ self.emit('LOAD_GLOBAL', '_getiter_')
+ self.emit('ROT_TWO')
+ self.emit('CALL_FUNCTION', 1)
+ self.emit('UNPACK_SEQUENCE', num)
+
+ def _visitAssSequence(self, node):
+ if findOp(node) != 'OP_DELETE':
+ self._gen_safe_unpack_sequence(len(node.nodes))
+ for child in node.nodes:
+ self.visit(child)
+
+ visitAssTuple = _visitAssSequence
+ visitAssList = _visitAssSequence
+
+ # Call to generate code for unpacking nested tuple arguments
+ # in function calls.
+
+ def unpackSequence(self, tup):
+ self._gen_safe_unpack_sequence(len(tup))
+ for elt in tup:
+ if isinstance(elt, tuple):
+ self.unpackSequence(elt)
+ else:
+ self._nameOp('STORE', elt)
+
+# A collection of code generators that adds the restricted mixin to
+# handle unpacking for all the different compilation modes. They
+# are defined here (at the end) so that can refer to RestrictedCodeGenerator.
+
+class RestrictedFunctionCodeGenerator(RestrictedCodeGenerator,
+ pycodegen.FunctionCodeGenerator):
+ pass
+
+class RestrictedExpressionCodeGenerator(RestrictedCodeGenerator,
+ pycodegen.ExpressionCodeGenerator):
+ pass
+
+class RestrictedInteractiveCodeGenerator(RestrictedCodeGenerator,
+ pycodegen.InteractiveCodeGenerator):
+ pass
+
+class RestrictedModuleCodeGenerator(RestrictedCodeGenerator,
+ pycodegen.ModuleCodeGenerator):
+
+ def initClass(self):
+ ModuleCodeGenerator.initClass(self)
+ self.__class__.FunctionGen = RestrictedFunctionCodeGenerator
+
+
+# These subclasses work around the definition of stub compile and mode
+# attributes in the common base class AbstractCompileMode. If it
+# didn't define new attributes, then the stub code inherited via
+# RestrictedCompileMode would override the real definitions in
+# Expression.
+
+class RExpression(RestrictedCompileMode, Expression):
+ mode = "eval"
+ CodeGeneratorClass = RestrictedExpressionCodeGenerator
+
+class RInteractive(RestrictedCompileMode, Interactive):
+ mode = "single"
+ CodeGeneratorClass = RestrictedInteractiveCodeGenerator
+
+class RModule(RestrictedCompileMode, Module):
+ mode = "exec"
+ CodeGeneratorClass = RestrictedModuleCodeGenerator
+
+class RFunction(RModule):
+ """A restricted Python function built from parts."""
+
+ CodeGeneratorClass = RestrictedModuleCodeGenerator
+
+ def __init__(self, p, body, name, filename, globals):
+ self.params = p
+ self.body = body
+ self.name = name
+ self.globals = globals or []
+ RModule.__init__(self, None, filename)
+
+ def parse(self):
+ # Parse the parameters and body, then combine them.
+ firstline = 'def f(%s): pass' % self.params
+ tree = niceParse(firstline, '<function parameters>', 'exec')
+ f = tree.node.nodes[0]
+ body_code = niceParse(self.body, self.filename, 'exec')
+ # Stitch the body code into the function.
+ f.code.nodes = body_code.node.nodes
+ f.name = self.name
+ # Look for a docstring.
+ stmt1 = f.code.nodes[0]
+ if (isinstance(stmt1, ast.Discard) and
+ isinstance(stmt1.expr, ast.Const) and
+ isinstance(stmt1.expr.value, str)):
+ f.doc = stmt1.expr.value
+ # The caller may specify that certain variables are globals
+ # so that they can be referenced before a local assignment.
+ # The only known example is the variables context, container,
+ # script, traverse_subpath in PythonScripts.
+ if self.globals:
+ f.code.nodes.insert(0, ast.Global(self.globals))
+ return tree