[Zope3-checkins] SVN: Zope3/branches/ZopeX3-3.0/ Merged from trunk: Support for untrusted page templates.

Fred L. Drake, Jr. fred at zope.com
Fri Jul 23 15:03:57 EDT 2004


Log message for revision 26720:
  Merged from trunk: Support for untrusted page templates.
  
  This merge contains a large number of patches from the Zope 3 trunk.
  These changes collectively cause page templates loaded from the file
  system to behave as trusted code (similar to what all page templates
  did previously, except for a few things), and page templates loaded
  from the database to be treated as untrusted code (so security
  declarations are honored during traversal and Python code execution).
  
  The following revisions are included in this merge:
  
  26175 - make python: expressions that don't compile raise right exception
  26637 - re-factor the base module importer for path:modules/
  26639 - explain the untrusted path:modules/
  26640 - clean up some boilerplate
  26642 - separate the execution engines for zope.app.pagetemplate
  26657 - add tests for trusted and untrusted path:modules/... expressions
  26679 - separate python expression compilation to allow override
  26688 - implemented restricted Python interpreter for TALES expressions
  26689 - add dependency on zope.restrictedpython
  26704 - implement untrusted traversal for page templates
  26719 - add note about page template security changes
  


Changed:
  U   Zope3/branches/ZopeX3-3.0/doc/CHANGES.txt
  A   Zope3/branches/ZopeX3-3.0/src/RestrictedPython/
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/Eval.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/Guards.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/Limits.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/MutatingWalker.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/PrintCollector.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/RCompile.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/RestrictionMutator.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/SafeMapping.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/SelectCompiler.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/Utilities.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/__init__.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/tests/__init__.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/tests/before_and_after.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/tests/class.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/tests/lambda.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/tests/restricted_module.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/tests/security_in_syntax.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/tests/testRestrictions.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/tests/unpack.py
  _U  Zope3/branches/ZopeX3-3.0/src/RestrictedPython/tests/verify.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/DEPENDENCIES.cfg
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/engine.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/metaconfigure.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/tests/test_engine.py
  A   Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/tests/trusted.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/viewpagetemplatefile.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/pagetemplateresource.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/traversing/ftests/test_vhosting.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/app/zptpage/tests/test_zptpage.py
  A   Zope3/branches/ZopeX3-3.0/src/zope/restrictedpython/
  _U  Zope3/branches/ZopeX3-3.0/src/zope/restrictedpython/README.txt
  _U  Zope3/branches/ZopeX3-3.0/src/zope/restrictedpython/__init__.py
  _U  Zope3/branches/ZopeX3-3.0/src/zope/restrictedpython/mutator.py
  _U  Zope3/branches/ZopeX3-3.0/src/zope/restrictedpython/rcompile.py
  _U  Zope3/branches/ZopeX3-3.0/src/zope/restrictedpython/tests.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/tales/expressions.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/tales/pythonexpr.py
  U   Zope3/branches/ZopeX3-3.0/src/zope/tales/tests/test_expressions.py


-=-
Modified: Zope3/branches/ZopeX3-3.0/doc/CHANGES.txt
===================================================================
--- Zope3/branches/ZopeX3-3.0/doc/CHANGES.txt	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/doc/CHANGES.txt	2004-07-23 19:03:56 UTC (rev 26720)
@@ -10,7 +10,10 @@
 
     New features
 
-    New Features
+      - Separated the trusted and untrusted behaviors of page
+        templates in the application server.  File-system-based
+        templates are trusted, and database-based templates are
+        untrusted.
 
       - Improved using documentation files as tests. (Needed to test
         new security policy):

Copied: Zope3/branches/ZopeX3-3.0/src/RestrictedPython (from rev 26688, Zope3/trunk/src/RestrictedPython)

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/DEPENDENCIES.cfg
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/DEPENDENCIES.cfg	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/DEPENDENCIES.cfg	2004-07-23 19:03:56 UTC (rev 26720)
@@ -17,6 +17,7 @@
 zope.pagetemplate
 zope.proxy
 zope.publisher
+zope.restrictedpython
 zope.schema
 zope.security
 zope.server

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/engine.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/engine.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/engine.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -18,39 +18,81 @@
 $Id$
 """
 import sys
-from types import StringTypes
 
+from zope.interface import implements
+
 from zope.tales.expressions import PathExpr, StringExpr, NotExpr, DeferExpr
+from zope.tales.expressions import SimpleModuleImporter
 from zope.tales.pythonexpr import PythonExpr
 from zope.tales.tales import ExpressionEngine, Context
 
 from zope.component.exceptions import ComponentLookupError
+from zope.exceptions import NotFoundError
 from zope.proxy import removeAllProxies
+from zope.restrictedpython import rcompile
 from zope.security.proxy import ProxyFactory
 from zope.security.builtins import RestrictedBuiltins
 from zope.i18n import translate
 
 from zope.app import zapi
 from zope.app.i18n import ZopeMessageIDFactory as _
-from zope.app.traversing.adapters import Traverser
-from zope.app.traversing.interfaces import IPathAdapter
+from zope.app.traversing.adapters import Traverser, traversePathElement
+from zope.app.traversing.interfaces import IPathAdapter, ITraversable
 
 class InlineCodeError(Exception):
     pass
 
+
 def zopeTraverser(object, path_items, econtext):
     """Traverses a sequence of names, first trying attributes then items.
     """
-    traverser = Traverser(object)
-    return traverser.traverse(path_items,
-                              request=getattr(econtext, 'request', None))
+    request = getattr(econtext, 'request', None)
+    path_items = list(path_items)
+    path_items.reverse()
 
+    while path_items:
+        name = path_items.pop()
+        object = traversePathElement(object, name, path_items,
+                                     request=request)
+        object = ProxyFactory(object)
+    return object
 
 class ZopePathExpr(PathExpr):
 
     def __init__(self, name, expr, engine):
         super(ZopePathExpr, self).__init__(name, expr, engine, zopeTraverser)
 
+
+def trustedZopeTraverser(object, path_items, econtext):
+    """Traverses a sequence of names, first trying attributes then items.
+    """
+    traverser = Traverser(object)
+    return traverser.traverse(path_items,
+                              request=getattr(econtext, 'request', None))
+
+class TrustedZopePathExpr(PathExpr):
+
+    def __init__(self, name, expr, engine):
+        super(TrustedZopePathExpr, self).__init__(name, expr, engine,
+                                                  trustedZopeTraverser)
+
+
+# Create a version of the restricted built-ins that uses a safe
+# 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):
@@ -58,17 +100,18 @@
         vars = self._bind_used_names(econtext, RestrictedBuiltins)
         return eval(self._code, vars)
 
-class ZopeContext(Context):
+    def _compile(self, text, filename):
+        return rcompile.compile(text, filename, 'eval')
 
-    def setContext(self, name, value):
-        # Hook to allow subclasses to do things like adding security proxies
-        Context.setContext(self, name, ProxyFactory(value))
 
+class ZopeContextBase(Context):
+    """Base class for both trusted and untrusted evaluation contexts."""
+
     def evaluateText(self, expr):
         text = self.evaluate(expr)
         if text is self.getDefault() or text is None:
             return text
-        if isinstance(text, StringTypes):
+        if isinstance(text, basestring):
             # text could be a proxied/wrapped object
             return text
         return unicode(text)
@@ -102,7 +145,7 @@
             error = _('No interpreter named "${lang_name}" was found.')
             error.mapping = {'lang_name': lang}
             raise InlineCodeError, error
-                  
+
         globals = self.vars.copy()
         result = interpreter.evaluateRawCode(code, globals)
         # Add possibly new global variables.
@@ -113,6 +156,18 @@
         return result
 
 
+class ZopeContext(ZopeContextBase):
+    """Evaluation context for untrusted programs."""
+
+    def setContext(self, name, value):
+        # Hook to allow subclasses to do things like adding security proxies
+        Context.setContext(self, name, ProxyFactory(value))
+
+
+class TrustedZopeContext(ZopeContextBase):
+    """Evaluation context for trusted programs."""
+
+
 class AdapterNamespaces(object):
     """Simulate tales function namespaces with adapter lookup.
 
@@ -143,7 +198,7 @@
 
 
     Cleanup:
-    
+
       >>> tearDown()
     """
 
@@ -158,12 +213,104 @@
                     return zapi.getAdapter(object, IPathAdapter, name)
                 except ComponentLookupError:
                     raise KeyError, name
-                
+
             self.namespaces[name] = namespace
         return namespace
 
+
 class ZopeEngine(ExpressionEngine):
+    """Untrusted expression engine.
 
+    This engine does not allow modules to be imported; only modules
+    already available may be accessed::
+
+      >>> modname = 'zope.app.pagetemplate.tests.trusted'
+      >>> engine = _Engine()
+      >>> context = engine.getContext(engine.getBaseNames())
+
+      >>> modname in sys.modules
+      False
+      >>> context.evaluate('modules/' + modname)
+      Traceback (most recent call last):
+        ...
+      KeyError: 'zope.app.pagetemplate.tests.trusted'
+
+    (The use of KeyError is an unfortunate implementation detail; I
+    think this should be a NotFoundError.)
+
+    Modules which have already been imported by trusted code are
+    available, wrapped in security proxies::
+
+      >>> m = context.evaluate('modules/sys')
+      >>> m.__name__
+      'sys'
+      >>> m._getframe
+      Traceback (most recent call last):
+        ...
+      ForbiddenAttribute: ('_getframe', <module 'sys' (built-in)>)
+
+    The results of Python expressions evaluated by this engine are
+    wrapped in security proxies::
+
+      >>> r = context.evaluate('python: {12: object()}.values')
+      >>> type(r)
+      <type 'zope.security._proxy._Proxy'>
+      >>> r = context.evaluate('python: {12: object()}.values()[0].__class__')
+      >>> type(r)
+      <type 'zope.security._proxy._Proxy'>
+
+    General path expressions provide objects that are wrapped in
+    security proxies as well::
+
+      >>> from zope.app.container.sample import SampleContainer
+      >>> from zope.app.tests.placelesssetup import setUp, tearDown
+      >>> from zope.security.checker import NamesChecker, defineChecker
+
+      >>> class Container(SampleContainer):
+      ...     implements(ITraversable)
+      ...     def traverse(self, name, further_path):
+      ...         return self[name]
+
+      >>> setUp()
+      >>> defineChecker(Container, NamesChecker(['traverse']))
+      >>> d = engine.getBaseNames()
+      >>> foo = Container()
+      >>> foo.__name__ = 'foo'
+      >>> d['foo'] = ProxyFactory(foo)
+      >>> foo['bar'] = bar = Container()
+      >>> bar.__name__ = 'bar'
+      >>> bar.__parent__ = foo
+      >>> bar['baz'] = baz = Container()
+      >>> baz.__name__ = 'baz'
+      >>> baz.__parent__ = bar
+      >>> context = engine.getContext(d)
+
+      >>> o1 = context.evaluate('foo/bar')
+      >>> o1.__name__
+      'bar'
+      >>> type(o1)
+      <type 'zope.security._proxy._Proxy'>
+
+      >>> o2 = context.evaluate('foo/bar/baz')
+      >>> o2.__name__
+      'baz'
+      >>> type(o2)
+      <type 'zope.security._proxy._Proxy'>
+      >>> o3 = o2.__parent__
+      >>> type(o3)
+      <type 'zope.security._proxy._Proxy'>
+      >>> o1 == o3
+      True
+
+      >>> o1 is o2
+      False
+
+      >>> tearDown()
+
+    """
+
+    _create_context = ZopeContext
+
     def __init__(self):
         ExpressionEngine.__init__(self)
         self.namespaces = AdapterNamespaces()
@@ -175,7 +322,7 @@
             else:
                 namespace = __namespace
 
-        context = ZopeContext(self, namespace)
+        context = self._create_context(self, namespace)
 
         # Put request into context so path traversal can find it
         if 'request' in namespace:
@@ -187,22 +334,93 @@
 
         return context
 
+
+class TrustedZopeEngine(ZopeEngine):
+    """Trusted expression engine.
+
+    This engine allows modules to be imported::
+
+      >>> modname = 'zope.app.pagetemplate.tests.trusted'
+      >>> engine = _TrustedEngine()
+      >>> context = engine.getContext(engine.getBaseNames())
+
+      >>> modname in sys.modules
+      False
+      >>> m = context.evaluate('modules/' + modname)
+      >>> m.__name__ == modname
+      True
+      >>> modname in sys.modules
+      True
+
+    Since this is trusted code, we can look at whatever is in the
+    module, not just __name__ or what's declared in a security
+    assertion::
+
+      >>> m.x
+      42
+
+    Clean up after ourselves::
+
+      >>> del sys.modules[modname]
+
+    """
+
+    _create_context = TrustedZopeContext
+
+
+class TraversableModuleImporter(SimpleModuleImporter):
+
+    implements(ITraversable)
+
+    def traverse(self, name, further_path):
+        try:
+            return self[name]
+        except KeyError:
+            raise NotFoundError(name)
+
+
 def _Engine(engine=None):
     if engine is None:
         engine = ZopeEngine()
-        
-    for pt in ZopePathExpr._default_type_names:
-        engine.registerType(pt, ZopePathExpr)
+    engine = _create_base_engine(engine, ZopePathExpr)
+    engine.registerType('python', ZopePythonExpr)
+
+    # Using a proxy around sys.modules allows page templates to use
+    # modules for which security declarations have been made, but
+    # disallows execution of any import-time code for modules, which
+    # should not be allowed to happen during rendering.
+    engine.registerBaseName('modules', ProxyFactory(sys.modules))
+
+    return engine
+
+def _TrustedEngine(engine=None):
+    if engine is None:
+        engine = TrustedZopeEngine()
+    engine = _create_base_engine(engine, TrustedZopePathExpr)
+    engine.registerType('python', PythonExpr)
+    engine.registerBaseName('modules', TraversableModuleImporter())
+    return engine
+
+def _create_base_engine(engine, pathtype):
+    for pt in pathtype._default_type_names:
+        engine.registerType(pt, pathtype)
     engine.registerType('string', StringExpr)
-    engine.registerType('python', ZopePythonExpr)
     engine.registerType('not', NotExpr)
     engine.registerType('defer', DeferExpr)
-    engine.registerBaseName('modules', ProxyFactory(sys.modules))
     return engine
 
+
 Engine = _Engine()
+TrustedEngine = _TrustedEngine()
 
+
 class AppPT(object):
 
     def pt_getEngine(self):
         return Engine
+
+
+class TrustedAppPT(object):
+
+    def pt_getEngine(self):
+        return TrustedEngine

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/metaconfigure.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/metaconfigure.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/metaconfigure.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -17,6 +17,7 @@
 $Id$
 """
 from zope.app.pagetemplate.engine import Engine, _Engine
+from zope.app.pagetemplate.engine import TrustedEngine, _TrustedEngine
 from zope.testing.cleanup import addCleanUp
 from zope.interface import Interface
 from zope.configuration.fields import GlobalObject
@@ -42,13 +43,19 @@
 def expressiontype(_context, name, handler):
     _context.action(
         discriminator = ("tales:expressiontype", name),
-        callable = Engine.registerType,
+        callable = registerType,
         args = (name, handler)
         )
 
+def registerType(name, handler):
+    Engine.registerType(name, handler)
+    TrustedEngine.registerType(name, handler)
 
+
 def clear():
     Engine.__init__()
     _Engine(Engine)
+    TrustedEngine.__init__()
+    _TrustedEngine(TrustedEngine)
 
 addCleanUp(clear)

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/tests/test_engine.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/tests/test_engine.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/tests/test_engine.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -19,9 +19,7 @@
 from zope.testing.doctestunit import DocTestSuite
 
 def test_suite():
-    return unittest.TestSuite((
-        DocTestSuite('zope.app.pagetemplate.engine'),
-        ))
+    return DocTestSuite('zope.app.pagetemplate.engine')
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')

Copied: Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/tests/trusted.py (from rev 26657, Zope3/trunk/src/zope/app/pagetemplate/tests/trusted.py)


Property changes on: Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/tests/trusted.py
___________________________________________________________________
Name: svn:mime-type
   + text/x-python
Name: svn:eol-style
   + native

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/viewpagetemplatefile.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/viewpagetemplatefile.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/pagetemplate/viewpagetemplatefile.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -20,9 +20,9 @@
 
 from zope.pagetemplate.pagetemplatefile import PageTemplateFile
 from zope.component import getView
-from zope.app.pagetemplate.engine import AppPT
+from zope.app.pagetemplate.engine import TrustedAppPT
 
-class ViewPageTemplateFile(AppPT, PageTemplateFile):
+class ViewPageTemplateFile(TrustedAppPT, PageTemplateFile):
     """Page Templates used as methods of views defined as Python classes.
     """
 

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/pagetemplateresource.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/pagetemplateresource.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/publisher/pagetemplateresource.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -17,9 +17,9 @@
 """
 
 from zope.pagetemplate.pagetemplatefile import PageTemplateFile
-from zope.app.pagetemplate.engine import AppPT
+from zope.app.pagetemplate.engine import TrustedAppPT
 
-class PageTemplate(AppPT, PageTemplateFile):
+class PageTemplate(TrustedAppPT, PageTemplateFile):
     """
     Resource that is a page template
     """

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/traversing/ftests/test_vhosting.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/traversing/ftests/test_vhosting.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/traversing/ftests/test_vhosting.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -23,7 +23,7 @@
 from transaction import get_transaction
 from zope.app.publisher.browser.resource import Resource
 from zope.app.traversing.api import traverse
-from zope.security.checker import defineChecker, NoProxy
+from zope.security.checker import defineChecker, NamesChecker, NoProxy
 from zope.app.container.contained import Contained
 from zope.app.zptpage.zptpage import ZPTPage
 
@@ -128,6 +128,7 @@
 
     def test_resources(self):
         ztapi.browserResource('quux', Resource)
+        defineChecker(Resource, NamesChecker(['__call__']))
         self.addPage('/foo/bar/pt',
                      u'<span tal:replace="context/++resource++quux" />')
         self.verify('/foo/bar/pt', 'http://localhost/@@/quux\n')

Modified: Zope3/branches/ZopeX3-3.0/src/zope/app/zptpage/tests/test_zptpage.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/app/zptpage/tests/test_zptpage.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/app/zptpage/tests/test_zptpage.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -99,6 +99,8 @@
                     return 'None'
                 return name
 
+        defineChecker(AU, NamesChecker(['__str__']))
+
         from zope.app.traversing.namespace import view
         ztapi.provideNamespaceHandler('view', view)
         ztapi.browserView(IZPTPage, 'name', AU)

Copied: Zope3/branches/ZopeX3-3.0/src/zope/restrictedpython (from rev 26688, Zope3/trunk/src/zope/restrictedpython)

Modified: Zope3/branches/ZopeX3-3.0/src/zope/tales/expressions.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/tales/expressions.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/tales/expressions.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -16,7 +16,6 @@
 $Id$
 """
 import re
-from types import StringTypes, TupleType
 
 from zope.interface import implements
 from zope.tales.tales import CompilerError
@@ -96,7 +95,7 @@
             # check for initial function
             raise CompilerError(
                 'Namespace function specified in first subpath element')
-        elif isinstance(first,StringTypes):
+        elif isinstance(first, basestring):
             # check for initial ?
             raise CompilerError(
                 'Dynamic name specified in first subpath element')
@@ -122,13 +121,13 @@
             ob = ob()
 
         for element in compiled_path:
-            if isinstance(element,TupleType):
+            if isinstance(element, tuple):
                 ob = self._traverser(ob, element, econtext)
-            elif isinstance(element,StringTypes):
+            elif isinstance(element, basestring):
                 val = vars[element]
                 # If the value isn't a string, assume it's a sequence
                 # of path names.
-                if isinstance(val,StringTypes):
+                if isinstance(val, basestring):
                     val = (val,)
                 ob = self._traverser(ob, val, econtext)
             elif callable(element):
@@ -303,8 +302,12 @@
     """Minimal module importer with no security."""
 
     def __getitem__(self, module):
-        mod = __import__(module)
+        mod = self._get_toplevel_module(module)
         path = module.split('.')
         for name in path[1:]:
             mod = getattr(mod, name)
         return mod
+
+    def _get_toplevel_module(self, module):
+        # This can be overridden to add security proxies.
+        return __import__(module)

Modified: Zope3/branches/ZopeX3-3.0/src/zope/tales/pythonexpr.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/tales/pythonexpr.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/tales/pythonexpr.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -20,10 +20,16 @@
     def __init__(self, name, expr, engine):
         text = ' '.join(expr.splitlines()).strip()
         self.text = text
-        # The next line can legally raise SyntaxError.
-        self._code = code = compile(text, '<string>', 'eval')
+        try:
+            code = self._compile(text, '<string>')
+        except SyntaxError, e:
+            raise engine.getCompilerError()(str(e))
+        self._code = code
         self._varnames = code.co_names
 
+    def _compile(self, text, filename):
+        return compile(text, filename, 'eval')
+
     def _bind_used_names(self, econtext, builtins):
         # Bind template variables
         names = {}

Modified: Zope3/branches/ZopeX3-3.0/src/zope/tales/tests/test_expressions.py
===================================================================
--- Zope3/branches/ZopeX3-3.0/src/zope/tales/tests/test_expressions.py	2004-07-23 18:57:48 UTC (rev 26719)
+++ Zope3/branches/ZopeX3-3.0/src/zope/tales/tests/test_expressions.py	2004-07-23 19:03:56 UTC (rev 26720)
@@ -131,7 +131,11 @@
         context=self.context
         self.assertEqual(expr(context), 4)
 
+    def testPythonErrorRaisesCompilerError(self):
+        self.assertRaises(self.engine.getCompilerError(),
+                          self.engine.compile, 'python: splat.0')
 
+
 class FunctionTests(ExpressionTestBase):
 
     def setUp(self):



More information about the Zope3-Checkins mailing list