[Zope3-checkins] SVN: Zope3/trunk/src/zope/ Refactored untrusted-python support:

Jim Fulton jim at zope.com
Wed Jul 28 15:37:35 EDT 2004


Log message for revision 26819:
  
    Refactored untrusted-python support:
  
    - collecting restricted builtins, restricted compilation, and
      the slightly higher level "interpreter" support in a single
      untrustedpython package.
  
    - Changed the way restricted builtins are handled so that we create an
      immutable module, rather than a dictionary.  This is to make
      __builtin__ immutable, to prevent evil code from changing it.
  
    - We now use Fred's restricted compiler rather than the basic Python
      compiler so that we can manipulate code to:
  
      - Make sure that the results of getattrs are proxied
  
      - Prevent use of exec, which we don't have code to handle yet.
        (Not sure if we'll bother.)
  
      - prevent use of raise or try/except, which we don't support yet,
        but will eventually.
  
      - Make sure that prints go to an interpreter-supplied object, rather
        than sys.stdout.
  
    - Updated varous clients to reflect new locations and APIs.
  
    - Had to work around some bugs in the Python compiler modules.
      Mote that global statements aren't handled correctly.
  
    I did this because the restricted interpreter was to be included in
    the X3.0 release and, even though it's not actually used, I was afraid
    that someone would try to use it.  Now, we've at least made an effort
    to get it right, although a more thorough review is needed.
  
  


Changed:
  U   Zope3/trunk/src/zope/app/DEPENDENCIES.cfg
  U   Zope3/trunk/src/zope/app/interpreter/python.py
  U   Zope3/trunk/src/zope/app/interpreter/tests/test_pythoninterpreter.py
  U   Zope3/trunk/src/zope/app/pagetemplate/engine.py
  U   Zope3/trunk/src/zope/app/pythonpage/__init__.py
  D   Zope3/trunk/src/zope/restrictedpython/
  D   Zope3/trunk/src/zope/security/builtins.py
  D   Zope3/trunk/src/zope/security/interpreter.py
  D   Zope3/trunk/src/zope/security/tests/test_builtins.py
  D   Zope3/trunk/src/zope/security/tests/test_interpreter.py
  A   Zope3/trunk/src/zope/security/untrustedpython/
  A   Zope3/trunk/src/zope/security/untrustedpython/__init__.py
  A   Zope3/trunk/src/zope/security/untrustedpython/builtins.py
  A   Zope3/trunk/src/zope/security/untrustedpython/builtins.txt
  A   Zope3/trunk/src/zope/security/untrustedpython/interpreter.py
  A   Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt
  A   Zope3/trunk/src/zope/security/untrustedpython/rcompile.py
  A   Zope3/trunk/src/zope/security/untrustedpython/rcompile.txt
  A   Zope3/trunk/src/zope/security/untrustedpython/tests.py
  U   Zope3/trunk/src/zope/tales/pythonexpr.py


-=-
Modified: Zope3/trunk/src/zope/app/DEPENDENCIES.cfg
===================================================================
--- Zope3/trunk/src/zope/app/DEPENDENCIES.cfg	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/DEPENDENCIES.cfg	2004-07-28 19:37:35 UTC (rev 26819)
@@ -18,7 +18,6 @@
 zope.pagetemplate
 zope.proxy
 zope.publisher
-zope.restrictedpython
 zope.schema
 zope.security
 zope.server

Modified: Zope3/trunk/src/zope/app/interpreter/python.py
===================================================================
--- Zope3/trunk/src/zope/app/interpreter/python.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/interpreter/python.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -15,12 +15,12 @@
 
 $Id$
 """
-import StringIO
+from StringIO import StringIO
 import sys
 
 from zope.app.interpreter.interfaces import IInterpreter
 from zope.interface import implements
-from zope.security.interpreter import RestrictedInterpreter
+from zope.security.untrustedpython.interpreter import exec_src, exec_code
 
 class PythonInterpreter(object):
 
@@ -28,18 +28,19 @@
 
     def evaluate(self, code, globals):
         """See zope.app.interfaces.IInterpreter"""
-        tmp = sys.stdout
-        sys.stdout = StringIO.StringIO()
-        ri = RestrictedInterpreter()
-        ri.globals = globals
-        try:
-            # This used to add a newline for Python 2.2. As far as 
-            # I know, we only care about 2.3 and later.
-            ri.ri_exec(code)
-        finally:
-            result = sys.stdout
-            sys.stdout = tmp
-        return result.getvalue()
+        tmp = StringIO()
+        globals['untrusted_output'] = tmp
+        if isinstance(code, basestring):
+            exec_src(code, globals,
+                     {}, # we don't want to get local assignments saved.
+                     )
+        else:
+            # XXX There atr no tests for this branch
+            code.exec_(globals,
+                       {}, # we don't want to get local assignments saved.
+                       )
+            
+        return tmp.getvalue()
         
 
     def evaluateRawCode(self, code, globals):

Modified: Zope3/trunk/src/zope/app/interpreter/tests/test_pythoninterpreter.py
===================================================================
--- Zope3/trunk/src/zope/app/interpreter/tests/test_pythoninterpreter.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/interpreter/tests/test_pythoninterpreter.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -62,11 +62,14 @@
         self._check(code, '')
         self.assert_('x' not in self.globals.keys())
 
-    def test_global_variable_assignment(self):
-        code = ('global x\n'
-                'x = "hello"\n')
-        self._check(code, '')
-        self.assertEqual(self.globals['x'], 'hello')
+# TODO: get global statements working
+# The compiler module, which we now rely on, doesn't handle global
+# statements correctly.
+##     def test_global_variable_assignment(self):
+##         code = ('global x\n'
+##                 'x = "hello"\n')
+##         self._check(code, '')
+##         self.assertEqual(self.globals['x'], 'hello')
 
     def test_wrapped_by_html_comment(self):
         self._check('<!-- print "hello" -->', 'hello\n', True)

Modified: Zope3/trunk/src/zope/app/pagetemplate/engine.py
===================================================================
--- Zope3/trunk/src/zope/app/pagetemplate/engine.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/pagetemplate/engine.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -29,9 +29,9 @@
 from zope.component.exceptions import ComponentLookupError
 from zope.exceptions import NotFoundError
 from zope.proxy import removeAllProxies
-from zope.restrictedpython import rcompile
+from zope.security.untrustedpython import rcompile
 from zope.security.proxy import ProxyFactory
-from zope.security.builtins import RestrictedBuiltins
+from zope.security.untrustedpython.builtins import SafeBuiltins
 from zope.i18n import translate
 
 from zope.app import zapi
@@ -81,23 +81,12 @@
 # version of getattr() that wraps values in security proxies where
 # appropriate:
 
-_marker = object()
 
-def safe_getattr(object, name, default=_marker):
-    if default is _marker:
-        return ProxyFactory(getattr(object, name))
-    else:
-        return ProxyFactory(getattr(object, name, default))
-
-RestrictedBuiltins = RestrictedBuiltins.copy()
-RestrictedBuiltins["getattr"] = safe_getattr
-
-
 class ZopePythonExpr(PythonExpr):
 
     def __call__(self, econtext):
         __traceback_info__ = self.text
-        vars = self._bind_used_names(econtext, RestrictedBuiltins)
+        vars = self._bind_used_names(econtext, SafeBuiltins)
         return eval(self._code, vars)
 
     def _compile(self, text, filename):

Modified: Zope3/trunk/src/zope/app/pythonpage/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/pythonpage/__init__.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/app/pythonpage/__init__.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -24,6 +24,7 @@
 from zope.interface import Interface, implements
 from zope.schema import SourceText, TextLine
 from zope.app.i18n import ZopeMessageIDFactory as _
+from zope.security.untrustedpython.interpreter import CompiledProgram
 
 
 class IPythonPage(Interface):
@@ -172,19 +173,31 @@
         self.__prepared_source = self.prepareSource(source)
 
         # Compile objects cannot be pickled
-        self._v_compiled = compile(self.__prepared_source,
-                                   self.__filename(), 'exec')
+        self._v_compiled = CompiledProgram(self.__prepared_source,
+                                           self.__filename())
 
     _tripleQuotedString = re.compile(
-        r"^([ \t]*)[uU]?([rR]?)(('''|\"\"\").*?\4)", re.MULTILINE | re.DOTALL)
+        r"^([ \t]*)[uU]?([rR]?)(('''|\"\"\")(.*)\4)", re.MULTILINE | re.DOTALL)
 
     def prepareSource(self, source):
         """Prepare source."""
         # compile() don't accept '\r' altogether
         source = source.replace("\r\n", "\n")
         source = source.replace("\r", "\n")
+        
+        if isinstance(source, unicode):
+
+            # Use special conversion function to work around
+            # compiler-module failure to handle unicode in literals
+
+            try:
+                source = source.encode('ascii')
+            except UnicodeEncodeError:
+                return self._tripleQuotedString.sub(_print_usrc, source)
+
         return self._tripleQuotedString.sub(r"\1print u\2\3", source)
 
+
     def getSource(self):
         """Get the original source code."""
         return self.__source
@@ -198,8 +211,8 @@
 
         # Compile objects cannot be pickled
         if not hasattr(self, '_v_compiled'):
-            self._v_compiled = compile(self.__prepared_source,
-                                       self.__filename(), 'exec')
+            self._v_compiled = CompiledProgram(self.__prepared_source,
+                                               self.__filename())
 
         kw['request'] = request
         kw['script'] = self
@@ -208,3 +221,13 @@
         service = zapi.getService(Utilities)
         interpreter = service.queryUtility(IInterpreter, 'text/server-python')
         return interpreter.evaluate(self._v_compiled, kw)
+
+def _print_usrc(match):
+    string = match.group(3)
+    raw = match.group(2)
+    if raw:
+        return match.group(1)+'print '+`string`
+    return match.group(1)+'print u'+match.group(3).encode('unicode-escape')
+
+    
+    

Deleted: Zope3/trunk/src/zope/security/builtins.py
===================================================================
--- Zope3/trunk/src/zope/security/builtins.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/builtins.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -1,103 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# 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
-#
-##############################################################################
-"""Protection of builtin objects.
-
-$Id$
-"""
-import sys
-
-def RestrictedBuiltins():
-
-    from zope.security.proxy import ProxyFactory
-    from zope.security.checker import NamesChecker
-
-    # It's better to say what is safe than it say what is not safe
-    _safe = [
-        'ArithmeticError', 'AssertionError', 'AttributeError',
-        'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',
-        'Exception', 'FloatingPointError', 'IOError', 'ImportError',
-        'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
-        'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented',
-        'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning',
-        'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError',
-        'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
-        'SystemExit', 'TabError', 'TypeError', 'UnboundLocalError',
-        'UnicodeError', 'UserWarning', 'ValueError', 'Warning',
-        'ZeroDivisionError',
-        '__debug__', '__doc__', '__name__', 'abs', 'apply', 'bool',
-        'buffer', 'callable', 'chr', 'classmethod', 'cmp', 'coerce',
-        'complex', 'copyright', 'credits', 'delattr',
-        'dict', 'divmod', 'filter', 'float', 'getattr',
-        'hasattr', 'hash', 'hex', 'id', 'int', 'isinstance',
-        'issubclass', 'iter', 'len', 'license', 'list',
-        'long', 'map', 'max', 'min', 'object', 'oct', 'ord', 'pow',
-        'property', 'quit', 'range', 'reduce', 'repr', 'round',
-        'setattr', 'slice', 'staticmethod', 'str', 'super', 'tuple',
-        'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip',
-        'True', 'False'
-        ]
-
-    # TODO: dir segfaults with a seg fault due to a bas tuple check in
-    # merge_class_dict in object.c. The assert macro seems to be doing
-    # the wrong think. Basically, if an object has bases, then bases
-    # is assumed to be a tuple.
-
-    # Anything that accesses an external file is a no no:
-    # 'open', 'execfile', 'file'
-
-    # We dont want restricted code to call exit: 'SystemExit', 'exit'
-
-    # Other no nos:
-    #    help prints
-    #    input does I/O
-    #    raw_input does I/O
-    #    intern's effect is too global
-    #    reload does import, TODO: doesn't it use __import__?
-
-    _builtinTypeChecker = NamesChecker(
-        ['__str__', '__repr__', '__name__', '__module__',
-         '__bases__', '__call__'])
-
-    import __builtin__
-
-    builtins = {}
-    for name in _safe:
-        value = getattr(__builtin__, name)
-        if isinstance(value, type):
-            value = ProxyFactory(value, _builtinTypeChecker)
-        else:
-            value = ProxyFactory(value)
-        builtins[name] = value
-
-    def __import__(name, globals=None, locals=None, fromlist=()):
-        # Waaa, we have to emulate __import__'s weird semantics.
-        try:
-            module = sys.modules[name]
-            if fromlist:
-                return module
-
-            l = name.find('.')
-            if l < 0:
-                return module
-
-            return sys.modules[name[:l]]
-
-        except KeyError:
-            raise ImportError(name)
-
-    builtins['__import__'] = ProxyFactory(__import__)
-
-    return builtins
-
-RestrictedBuiltins = RestrictedBuiltins()

Deleted: Zope3/trunk/src/zope/security/interpreter.py
===================================================================
--- Zope3/trunk/src/zope/security/interpreter.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/interpreter.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -1,33 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# 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 interpreter.
-
-TODO: This code needs a serious security review!!!
-
-$Id$
-"""
-from zope.security.builtins import RestrictedBuiltins
-
-class RestrictedInterpreter(object):
-
-    def __init__(self):
-        self.globals = {}
-        self.locals = {}
-
-    def ri_exec(self, code):
-        """Execute Python code in a restricted environment.
-
-        The value of code can be either source or binary code."""
-        self.globals['__builtins__'] = RestrictedBuiltins
-        exec code in self.globals, self.locals

Deleted: Zope3/trunk/src/zope/security/tests/test_builtins.py
===================================================================
--- Zope3/trunk/src/zope/security/tests/test_builtins.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/tests/test_builtins.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -1,44 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# 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 Builtins Tests
-
-$Id$
-"""
-
-from unittest import makeSuite, TestCase, main
-from zope.testing.cleanup import CleanUp # Base class w registry cleanup
-
-class Test(CleanUp, TestCase):
-
-    def test(self):
-        from zope.security.builtins import RestrictedBuiltins
-        from zope.security.interfaces import Forbidden
-
-        def e(expr):
-            return eval(expr, {'__builtins__': RestrictedBuiltins})
-
-        self.assertEqual(e('__import__("sys").__name__'), "sys")
-        self.assertEqual(e('__import__("zope.security").__name__'), "zope")
-        self.assertEqual(e(
-            '__import__("zope.security", {}, None, ["__doc__"]).__name__'),
-                         "zope.security")
-        self.assertRaises(Forbidden, e, '__import__("sys").exit')
-
-
-
-def test_suite():
-    return makeSuite(Test)
-
-if __name__=='__main__':
-    main(defaultTest='test_suite')

Deleted: Zope3/trunk/src/zope/security/tests/test_interpreter.py
===================================================================
--- Zope3/trunk/src/zope/security/tests/test_interpreter.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/tests/test_interpreter.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -1,78 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2003 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# 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.
-#
-##############################################################################
-"""Test Restricted Python Interpreter
-
-$Id$
-"""
-import unittest
-
-from zope.security.interpreter import RestrictedInterpreter
-from zope.security.checker import defineChecker
-
-from zope.testing.cleanup import CleanUp
-
-class RITests(unittest.TestCase, CleanUp):
-
-    def setUp(self):
-        CleanUp.setUp(self)
-        self.rinterp = RestrictedInterpreter()
-
-    def tearDown(self):
-        CleanUp.tearDown(self)
-
-    def testExec(self):
-        self.rinterp.ri_exec("str(type(1))\n")
-
-    def testImport(self):
-        self.rinterp.ri_exec("import zope.security.proxy")
-
-    def testWrapping(self):
-        # make sure we've really got proxies
-        import types
-        from zope.security.checker import NamesChecker
-
-        checker = NamesChecker(['Proxy'])
-
-        import zope.security.proxy
-        defineChecker(zope.security.proxy, checker)
-
-        checker = NamesChecker(['BuiltinFunctionType'])
-        defineChecker(types, checker)
-
-        code = ("from zope.security.proxy import Proxy\n"
-                "import types\n"
-                "assert type(id) is not types.BuiltinFunctionType\n"
-                )
-        self.rinterp.ri_exec(code)
-
-    def testGlobalVersusLocal(self):
-        code = ("global x\n"
-                "x = 1\n"
-                "y = 2\n")
-        self.rinterp.ri_exec(code)
-        self.assert_('x' in self.rinterp.globals)
-        self.assert_('y' not in self.rinterp.globals)
-        self.assertEqual(self.rinterp.globals['x'], 1)
-        self.assert_('x' not in self.rinterp.locals)
-        self.assert_('y' in self.rinterp.locals)
-        self.assertEqual(self.rinterp.locals['y'], 2)
-
-
-def test_suite():
-    return unittest.makeSuite(RITests)
-
-
-if __name__=='__main__':
-    from unittest import main
-    main(defaultTest='test_suite')

Added: Zope3/trunk/src/zope/security/untrustedpython/__init__.py
===================================================================
--- Zope3/trunk/src/zope/security/untrustedpython/__init__.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/untrustedpython/__init__.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1 @@
+#


Property changes on: Zope3/trunk/src/zope/security/untrustedpython/__init__.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Copied: Zope3/trunk/src/zope/security/untrustedpython/builtins.py (from rev 26720, Zope3/trunk/src/zope/security/builtins.py)
===================================================================
--- Zope3/trunk/src/zope/security/builtins.py	2004-07-23 19:03:56 UTC (rev 26720)
+++ Zope3/trunk/src/zope/security/untrustedpython/builtins.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,122 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# 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
+#
+##############################################################################
+"""Protection of builtin objects.
+
+$Id$
+"""
+from zope.security.proxy import ProxyFactory
+import new
+
+def SafeBuiltins():
+
+    builtins = {}
+    
+    from zope.security.checker import NamesChecker
+    import __builtin__
+
+    _builtinTypeChecker = NamesChecker(
+        ['__str__', '__repr__', '__name__', '__module__',
+         '__bases__', '__call__'])
+
+    # It's better to say what is safe than it say what is not safe
+    for name in [
+
+        # Names of safe objects. See untrustedinterpreter.txt for a
+        # definition of safe objects.
+
+        'ArithmeticError', 'AssertionError', 'AttributeError',
+        'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError',
+        'Exception', 'FloatingPointError', 'IOError', 'ImportError',
+        'IndentationError', 'IndexError', 'KeyError', 'KeyboardInterrupt',
+        'LookupError', 'MemoryError', 'NameError', 'None', 'NotImplemented',
+        'NotImplementedError', 'OSError', 'OverflowError', 'OverflowWarning',
+        'ReferenceError', 'RuntimeError', 'RuntimeWarning', 'StandardError',
+        'StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError',
+        'SystemExit', 'TabError', 'TypeError', 'UnboundLocalError',
+        'UnicodeError', 'UserWarning', 'ValueError', 'Warning',
+        'ZeroDivisionError',
+        '__debug__', '__name__', '__doc__', 'abs', 'apply', 'bool',
+        'buffer', 'callable', 'chr', 'classmethod', 'cmp', 'coerce',
+        'complex', 'copyright', 'credits', 'delattr',
+        'dict', 'divmod', 'filter', 'float', 'getattr',
+        'hasattr', 'hash', 'hex', 'id', 'int', 'isinstance',
+        'issubclass', 'iter', 'len', 'license', 'list',
+        'long', 'map', 'max', 'min', 'object', 'oct', 'ord', 'pow',
+        'property', 'quit', 'range', 'reduce', 'repr', 'round',
+        'setattr', 'slice', 'staticmethod', 'str', 'super', 'tuple',
+        'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip',
+        'True', 'False',
+
+        # TODO: dir segfaults with a seg fault due to a bas tuple
+        # check in merge_class_dict in object.c. The assert macro
+        # seems to be doing the wrong think. Basically, if an object
+        # has bases, then bases is assumed to be a tuple.
+        #dir,
+        ]:
+
+        value = getattr(__builtin__, name)
+        if isinstance(value, type):
+            value = ProxyFactory(value, _builtinTypeChecker)
+        else:
+            value = ProxyFactory(value)
+        builtins[name] = value
+
+    from sys import modules
+
+    def _imp(name, fromlist, prefix=''):
+        module = modules.get(prefix+name)
+        if module is not None:
+            if fromlist or ('.' not in name):
+                return module
+            return modules[prefix+name.split('.')[0]]
+
+    def __import__(name, globals=None, locals=None, fromlist=()):
+        # Waaa, we have to emulate __import__'s weird semantics.
+
+        if globals:
+            __name__ = globals.get('__name__')
+            if __name__:
+                # Maybe do a relative import
+                if '__path__' not in globals:
+                    # We have an ordinary module, not a package,
+                    # so remove last name segment:
+                    __name__ = '.'.join(__name__.split('.')[:-1])
+                if __name__:
+                    module = _imp(name, fromlist, __name__+'.')
+                    if module is not None:
+                        return module
+
+        module = _imp(name, fromlist)
+        if module is not None:
+            return module
+
+        raise ImportError(name)
+
+    builtins['__import__'] = ProxyFactory(__import__)
+
+    return builtins
+
+class ImmutableModule(new.module):
+    def __init__(self, name='__builtins__', **kw):
+        new.module.__init__(self, name)
+        self.__dict__.update(kw)
+
+    def __setattr__(self, name, v):
+        raise AttributeError, name
+
+    def __delattr__(self, name):
+        raise AttributeError, name
+
+
+SafeBuiltins = ImmutableModule(**SafeBuiltins())

Added: Zope3/trunk/src/zope/security/untrustedpython/builtins.txt
===================================================================
--- Zope3/trunk/src/zope/security/untrustedpython/builtins.txt	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/untrustedpython/builtins.txt	2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,114 @@
+Safe Builtins
+=============
+
+When executing untrusted Python code, we need to make sure that we
+only give the code access to safe, basic or proxied objects. This
+included the builtin objects provided to Python code through a special
+__builtins__ module in globals.  The `builtins` module provides a
+suitable module object:
+
+  >>> from zope.security.untrustedpython.builtins import SafeBuiltins
+  >>> d = {'__builtins__': SafeBuiltins}
+  >>> exec 'x = str(1)' in d
+  >>> d['x']
+  '1'
+
+The object is immutable:
+
+  >>> SafeBuiltins.foo = 1
+  Traceback (most recent call last):
+  ...
+  AttributeError: foo
+
+  >>> del SafeBuiltins['getattr']
+  Traceback (most recent call last):
+  ...
+  TypeError: object does not support item deletion
+
+
+
+  Exception raised:
+  ...
+  TypeError: object does not support item deletion
+
+(Note that you can mutate it through its `__dict__` attribute,
+ however, when combined with the untrusted code compiler, getting the
+ `__dict__` attribute will return a proxied object that will prevent
+ mutation.) 
+
+It contains items with keys that are all strings and values that are
+either proxied or are basic types:
+
+  >>> from zope.security.proxy import Proxy
+  >>> for key, value in SafeBuiltins.__dict__.items():
+  ...     if not isinstance(key, str):
+  ...         raise TypeError(key)
+  ...     if value is not None and not isinstance(value, (Proxy, int, str)):
+  ...         raise TypeError(value, key)
+
+It doesn't contain unsafe items, such as eval, globals, etc:
+
+  >>> SafeBuiltins.eval
+  Traceback (most recent call last):
+  ...
+  AttributeError: 'ImmutableModule' object has no attribute 'eval'
+  >>> SafeBuiltins.globals
+  Traceback (most recent call last):
+  ...
+  AttributeError: 'ImmutableModule' object has no attribute 'globals'
+
+The safe builtins also contains a custom __import__ function.
+
+  >>> imp = SafeBuiltins.__import__
+  
+As with regular import, it only returns the top-level package if no
+fromlist is specified:
+
+  >>> import zope.security
+  >>> imp('zope.security') == zope
+  True
+  >>> imp('zope.security', {}, {}, ['*']) == zope.security
+  True
+
+Note that the values returned are proxied:
+
+  >>> type(imp('zope.security')) is Proxy
+  True
+  
+This means that, having imported a module, you will only be able to
+access attributes for which you are authorized.
+
+Unlike regular __import__, you can nly import modules that have been
+previously imported.  This is to prevent unauthorized execution of
+module-initialization code:
+
+  >>> security = zope.security
+  >>> import sys
+  >>> del sys.modules['zope.security'] 
+  >>> imp('zope.security')
+  Traceback (most recent call last):
+  ...
+  ImportError: zope.security
+
+  >>> sys.modules['zope.security'] = security
+
+Package-relative imports are supported (for now):
+
+  >>> imp('security', {'__name__': 'zope', '__path__': []}) == security
+  True
+  >>> imp('security', {'__name__': 'zope.foo'}) == zope.security
+  True
+
+  >>> imp('security.untrustedpython', {'__name__': 'zope.foo'}) == security
+  True
+  >>> from zope.security import untrustedpython
+  >>> imp('security.untrustedpython', {'__name__': 'zope.foo'}, {}, ['*']
+  ...     ) == untrustedpython
+  True
+  
+
+
+
+
+
+


Property changes on: Zope3/trunk/src/zope/security/untrustedpython/builtins.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: Zope3/trunk/src/zope/security/untrustedpython/interpreter.py (from rev 26795, Zope3/trunk/src/zope/security/interpreter.py)
===================================================================
--- Zope3/trunk/src/zope/security/interpreter.py	2004-07-27 15:15:58 UTC (rev 26795)
+++ Zope3/trunk/src/zope/security/untrustedpython/interpreter.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,78 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# 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 interpreter.
+
+TODO: This code needs a serious security review!!!
+
+$Id$
+"""
+from zope.security.untrustedpython.builtins import SafeBuiltins
+from zope.security.untrustedpython.rcompile import compile
+import warnings
+
+class RestrictedInterpreter(object):
+
+    def __init__(self):
+        warnings.warn("RestrictedInterpreter was deprecated 2004/7/27",
+                      DeprecationWarning, 2)
+        self.globals = {}
+        self.locals = {}
+
+    def ri_exec(self, code):
+        """Execute Python code in a restricted environment.
+
+        The value of code can be either source or binary code."""
+        if isinstance(code, basestring):
+            code = compile(code, '<string>', 'exec')
+        self.globals['__builtins__'] = SafeBuiltins
+        exec code in self.globals, self.locals
+
+def exec_code(code, globals, locals=None):
+    globals['__builtins__'] = SafeBuiltins
+    exec code in globals, locals
+
+def exec_src(source, globals, locals=None):
+    globals['__builtins__'] = SafeBuiltins
+    code = compile(source, '<string>', 'exec')
+    exec code in globals, locals
+    
+    
+class CompiledExpression(object):
+    """A compiled expression
+    """
+
+    def __init__(self, source, filename='<string>'):
+        self.source = source
+        self.code = compile(source, filename, 'eval')
+
+    def eval(self, globals, locals=None):
+        globals['__builtins__'] = SafeBuiltins
+        if locals is None:
+            return eval(self.code, globals)
+        else:
+            return eval(self.code, globals)
+    
+class CompiledProgram(object):
+    """A compiled expression
+    """
+
+    def __init__(self, source, filename='<string>'):
+        self.source = source
+        self.code = compile(source, filename, 'exec')
+
+    def exec_(self, globals, locals=None, output=None):
+        globals['__builtins__'] = SafeBuiltins
+        if output is not None:
+            globals['untrusted_output'] = output
+        exec self.code in globals, locals

Added: Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt
===================================================================
--- Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt	2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,112 @@
+Untrusted Python interpreter
+============================
+
+The interpreter module provides very basic Python interpreter
+support.  It combined untrusted code compilation with safe builtins
+and an exec-like API.  The exec_src function can be used to execute
+Python source:
+
+  >>> from zope.security.untrustedpython.interpreter import exec_src
+  >>> d = {}
+  >>> exec_src("x=1", d)
+  >>> d['x']
+  1
+
+  >>> exec_src("x=getattr", d)
+
+
+Note that the safe builtins dictionary is inserted into the
+dictionary:
+
+  >>> from zope.security.untrustedpython.builtins import SafeBuiltins
+  >>> d['__builtins__'] == SafeBuiltins
+  True
+
+All of the non-basic items in the safe builtins are proxied:
+
+  >>> exec_src('str=str', d)
+  >>> from zope.security.proxy import Proxy
+  >>> type(d['str']) is Proxy
+  True
+
+Note that, while you can get to the safe `__builtins__`'s dictionary,
+you can't use the dictionary to mutate it:
+
+  >>> from zope.security.interfaces import ForbiddenAttribute
+
+  >>> try: exec_src('__builtins__.__dict__["x"] = 1', d)
+  ... except ForbiddenAttribute: print 'Forbidden!'
+  Forbidden!
+
+  >>> try: exec_src('del __builtins__.__dict__["str"]', d)
+  ... except ForbiddenAttribute: print 'Forbidden!'
+  Forbidden!
+
+  >>> try: exec_src('__builtins__.__dict__.update({"x": 1})', d)
+  ... except ForbiddenAttribute: print 'Forbidden!'
+  Forbidden!
+
+Because the untrusted code compiler is used, you can't use exec,
+raise, or try/except statements:
+
+  >>> exec_src("exec 'x=1'", d)
+  Traceback (most recent call last):
+  ...
+  SyntaxError: Line 1: exec statements are not supported
+  
+Any attribute-access results will be proxied:
+
+  >>> exec_src("data = {}\nupdate = data.update\nupdate({'x': 'y'})", d)
+  >>> type(d['update']) is Proxy
+  True
+
+In this case, we were able to get to and use the update method because
+the data dictionary itself was created by the untrusted code and was,
+thus, unproxied.
+
+You can compile code yourself and call exec_code instead:
+
+  >>> from zope.security.untrustedpython.rcompile import compile
+  >>> code = compile('x=2', '<mycode>', 'exec')
+  >>> d = {}
+  >>> from zope.security.untrustedpython.interpreter import exec_code
+  >>> exec_code(code, d)
+  >>> d['x']
+  2
+
+This is useful if you are going to be executing the same expression
+many times, as you can avoid the cost of repeated comilation.
+
+Compiled Programs
+-----------------
+
+A slightly higher-level interface is provided by compiled programs.
+These make it easier to safetly safe the results of compilation:
+
+  >>> from zope.security.untrustedpython.interpreter import CompiledProgram
+  >>> p = CompiledProgram('x=2')
+  >>> d = {}
+  >>> p.exec_(d)
+  >>> d['x']
+  2
+    
+When you execute a compiled program, you can supply an object with a
+write method to get print output:
+
+  >>> p = CompiledProgram('print "Hello world!"')
+  >>> import cStringIO
+  >>> f = cStringIO.StringIO()
+  >>> p.exec_({}, output=f)
+  >>> f.getvalue()
+  'Hello world!\n'
+
+
+Compiled Expressions
+--------------------
+
+You can also precompile expressions:
+
+  >>> from zope.security.untrustedpython.interpreter import CompiledExpression
+  >>> p = CompiledExpression('x*2')
+  >>> p.eval({'x': 2})
+  4


Property changes on: Zope3/trunk/src/zope/security/untrustedpython/interpreter.txt
___________________________________________________________________
Name: svn:eol-style
   + native

Copied: Zope3/trunk/src/zope/security/untrustedpython/rcompile.py (from rev 26795, Zope3/trunk/src/zope/restrictedpython/rcompile.py)
===================================================================
--- Zope3/trunk/src/zope/restrictedpython/rcompile.py	2004-07-27 15:15:58 UTC (rev 26795)
+++ Zope3/trunk/src/zope/security/untrustedpython/rcompile.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# 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.
+#
+##############################################################################
+"""compile() equivalent that produces restricted code.
+
+Only 'eval' is supported at this time.
+
+$Id$
+"""
+
+import compiler.pycodegen
+
+import RestrictedPython.RCompile
+from RestrictedPython.SelectCompiler import ast, OP_ASSIGN, OP_DELETE, OP_APPLY
+
+def compile(text, filename, mode):
+    if not isinstance(text, basestring):
+        raise TypeError("Compiled source must be string")
+    gen = RExpression(text, str(filename), mode)
+    gen.compile()
+    return gen.getCode()
+
+class RExpression(RestrictedPython.RCompile.RestrictedCompileMode):
+
+    CodeGeneratorClass = compiler.pycodegen.ExpressionCodeGenerator
+
+    def __init__(self, source, filename, mode = "eval"):
+        self.mode = mode
+        RestrictedPython.RCompile.RestrictedCompileMode.__init__(
+            self, source, filename)
+        self.rm = RestrictionMutator()
+
+
+# The security checks are performed by a set of six functions that
+# must be provided by the restricted environment.
+
+_getattr_name = ast.Name("getattr")
+
+
+class RestrictionMutator:
+
+    def __init__(self):
+        self.errors = []
+        self.warnings = []
+        self.used_names = {}
+
+    def error(self, node, info):
+        """Records a security error discovered during compilation."""
+        lineno = getattr(node, 'lineno', None)
+        if lineno is not None and lineno > 0:
+            self.errors.append('Line %d: %s' % (lineno, info))
+        else:
+            self.errors.append(info)
+
+    def visitGetattr(self, node, walker):
+        """Converts attribute access to a function call.
+
+        'foo.bar' becomes 'getattr(foo, "bar")'.
+
+        Also prevents augmented assignment of attributes, which would
+        be difficult to support correctly.
+        """
+        node = walker.defaultVisitNode(node)
+        return ast.CallFunc(_getattr_name,
+                            [node.expr, ast.Const(node.attrname)])
+
+    def visitExec(self, node, walker):
+        self.error(node, "exec statements are not supported")
+
+    def visitPrint(self, node, walker):
+        """Make sure prints always have a destination
+
+        If we get a print without a destination, make the default destination
+        untrusted_output.
+        """
+        node = walker.defaultVisitNode(node)
+        if node.dest is None:
+            node.dest = ast.Name('untrusted_output')
+        return node
+    visitPrintnl = visitPrint
+        
+    def visitRaise(self, node, walker):
+        self.error(node, "raise statements are not supported")
+
+    def visitTryExcept(self, node, walker):
+        self.error(node, "try/except statements are not supported")
+                   

Copied: Zope3/trunk/src/zope/security/untrustedpython/rcompile.txt (from rev 26795, Zope3/trunk/src/zope/restrictedpython/README.txt)
===================================================================
--- Zope3/trunk/src/zope/restrictedpython/README.txt	2004-07-27 15:15:58 UTC (rev 26795)
+++ Zope3/trunk/src/zope/security/untrustedpython/rcompile.txt	2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,128 @@
+==================================
+Support for Restricted Python Code
+==================================
+
+This package provides a way to compile
+untrusted Python code so that it can be executed safely.
+
+This form of restricted Python assumes that security proxies will be
+used to protect assets.  Given this, the only thing that actually
+needs to be done differently by the generated code is to:
+
+- Ensure that all attribute lookups go through a safe version of the getattr()
+  function that's been provided in the built-in functions used in the
+  execution environment.  
+
+- Prevent exec statements. (Later, we could possibly make exec safe.)
+
+- Print statements always go to an output that is provided as a
+  global, rather than having an implicit sys.output.
+
+- Prevent try/except and raise statements. This is mainly because they
+  don't work properly in the presense of security proxies.  Try/except
+  statements will be made to work in the future.
+
+No other special treatment is needed to support safe expression
+evaluation.
+
+The implementation makes use of the `RestrictedPython` package,
+originally written for Zope 2.  There is a new AST re-writer in
+`zope.security.untrustedpython.rcompile` which performs the
+tree-transformation, and a top-level `compile()` function in
+`zope.security.untrustedpython.rcompile`; the later is what client
+applications are expected to use.
+
+The signature of the `compile()` function is very similar to that of
+Python's built-in `compile()` function::
+
+  compile(source, filename, mode)
+
+Using it is equally simple::
+
+  >>> from zope.security.untrustedpython.rcompile import compile
+
+  >>> code = compile("21 * 2", "<string>", "eval")
+  >>> eval(code)
+  42
+
+What's interesting about the restricted code is that all attribute
+lookups go through the `getattr()` function.  This is generally
+provided as a built-in function in the restricted environment::
+
+  >>> def mygetattr(object, name, default="Yahoo!"):
+  ...     marker = []
+  ...     print "Looking up", name
+  ...     if getattr(object, name, marker) is marker:
+  ...         return default
+  ...     else:
+  ...         return "Yeehaw!"
+
+  >>> import __builtin__
+  >>> builtins = __builtin__.__dict__.copy()
+  >>> builtins["getattr"] = mygetattr
+
+  >>> def reval(source):
+  ...     code = compile(source, "README.txt", "eval")
+  ...     globals = {"__builtins__": builtins}
+  ...     return eval(code, globals, {})
+
+  >>> reval("(42).__class__")
+  Looking up __class__
+  'Yeehaw!'
+  >>> reval("(42).not_really_there")
+  Looking up not_really_there
+  'Yahoo!'
+  >>> reval("(42).foo.not_really_there")
+  Looking up foo
+  Looking up not_really_there
+  'Yahoo!'
+
+This allows a `getattr()` to be used that ensures the result of
+evaluation is a security proxy.
+
+To compile code with statements, use exec or single:
+
+  >>> exec compile("x = 1", "<string>", "exec")
+  >>> x
+  1
+
+Trying to compile exec, raise or try/except sattements gives
+syntax errors:
+
+  >>> compile("exec 'x = 2'", "<string>", "exec")
+  Traceback (most recent call last):
+  ...
+  SyntaxError: Line 1: exec statements are not supported
+
+  >>> compile("raise KeyError('x')", "<string>", "exec")
+  Traceback (most recent call last):
+  ...
+  SyntaxError: Line 1: raise statements are not supported
+
+  >>> compile("try: pass\nexcept: pass", "<string>", "exec")
+  Traceback (most recent call last):
+  ...
+  SyntaxError: Line 1: try/except statements are not supported
+
+Printing to an explicit writable is allowed:
+
+  >>> import StringIO
+  >>> f = StringIO.StringIO()
+  >>> code = compile("print >> f, 'hi',\nprint >> f, 'world'", '', 'exec')
+  >>> exec code in {'f': f}
+  >>> f.getvalue()
+  'hi world\n'
+
+But if no output is specified, then output will be send to
+`untrusted_output`:
+
+  >>> code = compile("print 'hi',\nprint 'world'", '', 'exec')
+  >>> exec code in {}
+  Traceback (most recent call last):
+  ...
+  NameError: name 'untrusted_output' is not defined
+
+  >>> f = StringIO.StringIO()
+  >>> exec code in {'untrusted_output': f}
+
+

Added: Zope3/trunk/src/zope/security/untrustedpython/tests.py
===================================================================
--- Zope3/trunk/src/zope/security/untrustedpython/tests.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/security/untrustedpython/tests.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -0,0 +1,31 @@
+##############################################################################
+#
+# Copyright (c) 2004 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.
+#
+##############################################################################
+"""Untrusted python tests
+
+$Id$
+"""
+import unittest
+from zope.testing import doctestunit
+
+def test_suite():
+    return unittest.TestSuite((
+        doctestunit.DocFileSuite('builtins.txt',
+                                 'rcompile.txt',
+                                 'interpreter.txt',
+                                 ),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: Zope3/trunk/src/zope/security/untrustedpython/tests.py
___________________________________________________________________
Name: svn:keywords
   + Id
Name: svn:eol-style
   + native

Modified: Zope3/trunk/src/zope/tales/pythonexpr.py
===================================================================
--- Zope3/trunk/src/zope/tales/pythonexpr.py	2004-07-28 19:35:56 UTC (rev 26818)
+++ Zope3/trunk/src/zope/tales/pythonexpr.py	2004-07-28 19:37:35 UTC (rev 26819)
@@ -18,7 +18,8 @@
 
 class PythonExpr(object):
     def __init__(self, name, expr, engine):
-        text = ' '.join(expr.splitlines()).strip()
+        text = '\n'.join(expr.splitlines()) # normalize line endings
+        text = '(' + text + ')' # Put text in parens so newlines don't matter
         self.text = text
         try:
             code = self._compile(text, '<string>')
@@ -36,6 +37,8 @@
         names = {}
         vars = econtext.vars
         marker = self
+        if not isinstance(builtins, dict):
+            builtins = builtins.__dict__
         for vname in self._varnames:
             val = vars.get(vname, marker)
             if val is not marker:



More information about the Zope3-Checkins mailing list