added attr filter
authorArmin Ronacher <armin.ronacher@active-4.com>
Mon, 26 May 2008 11:35:58 +0000 (13:35 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Mon, 26 May 2008 11:35:58 +0000 (13:35 +0200)
--HG--
branch : trunk

jinja2/filters.py
jinja2/runtime.py
jinja2/sandbox.py
tests/test_security.py
tests/test_various.py

index de15b5318baac4dc63a40e27b1ba91ee1e44d740..0a32f9ae30640874d92122996494bf6b385f571b 100644 (file)
@@ -16,7 +16,7 @@ from operator import itemgetter
 from itertools import imap, groupby
 from jinja2.utils import Markup, escape, pformat, urlize, soft_unicode
 from jinja2.runtime import Undefined
-from jinja2.exceptions import FilterArgumentError
+from jinja2.exceptions import FilterArgumentError, SecurityError
 
 
 _word_re = re.compile(r'\w+')
@@ -620,7 +620,29 @@ def do_reverse(value):
             raise FilterArgumentError('argument must be iterable')
 
 
+@environmentfilter
+def do_attr(environment, obj, name):
+    """Get an attribute of an object.  ``foo|attr("bar")`` works like
+    ``foo["bar"]`` just that always an attribute is returned.  This is useful
+    if data structures are passed to the template that have an item that hides
+    an attribute with the same name.  For example a dict ``{'items': []}``
+    that obviously hides the item method of a dict.
+    """
+    try:
+        value = getattr(obj, name)
+    except AttributeError:
+        return environment.undefined(obj=obj, name=name)
+    if environment.sandboxed and not \
+       environment.is_safe_attribute(obj, name, value):
+        return environment.undefined('access to attribute %r of %r '
+                                     'object is unsafe.' % (
+            name, obj.__class__.__name__
+        ), name=name, obj=obj, exc=SecurityError)
+    return value
+
+
 FILTERS = {
+    'attr':                 do_attr,
     'replace':              do_replace,
     'upper':                do_upper,
     'lower':                do_lower,
index 7a1cf276d98933680a20893967c7110966ae5229..f5639f8d5f34296212653b236bd364a037c10def 100644 (file)
@@ -9,6 +9,7 @@
     :license: GNU GPL.
 """
 import sys
+from types import FunctionType, MethodType
 from itertools import chain, imap
 from jinja2.utils import Markup, partial, soft_unicode, escape, missing, concat
 from jinja2.exceptions import UndefinedError, TemplateRuntimeError
@@ -19,6 +20,8 @@ __all__ = ['LoopContext', 'Context', 'TemplateReference', 'Macro', 'Markup',
            'TemplateRuntimeError', 'missing', 'concat', 'escape',
            'markup_join', 'unicode_join']
 
+_context_function_types = (FunctionType, MethodType)
+
 
 def markup_join(seq):
     """Concatenation that escapes if necessary and converts to unicode."""
@@ -118,10 +121,13 @@ class Context(object):
         or environment as first arguments.  Then forwards the call to
         the object with the arguments and keyword arguments.
         """
-        if getattr(__obj, 'contextfunction', 0):
-            args = (__self,) + args
-        elif getattr(__obj, 'environmentfunction', 0):
-            args = (__self.environment,) + args
+        if __debug__:
+            __traceback_hide__ = True
+        if isinstance(__obj, _context_function_types):
+            if getattr(__obj, 'contextfunction', 0):
+                args = (__self,) + args
+            elif getattr(__obj, 'environmentfunction', 0):
+                args = (__self.environment,) + args
         return __obj(*args, **kwargs)
 
     def _all(meth):
index ce3369b31d7d5da1a7c6fe7abc63152020c84653..d44521ed604f6b0725d01e24348259b827d8f259 100644 (file)
@@ -184,7 +184,7 @@ class SandboxedEnvironment(Environment):
                                               'object is unsafe.' % (
                             argument,
                             obj.__class__.__name__
-                        ), name=argument, exc=SecurityError)
+                        ), name=argument, obj=obj, exc=SecurityError)
         return self.undefined(obj=obj, name=argument)
 
     def call(__self, __context, __obj, *args, **kwargs):
index 68b1515743923022c85549bda9bcfcc5aebd995e..7c812c0942e357beccce07763e96d8cbacd9139a 100644 (file)
@@ -6,10 +6,12 @@
     :copyright: 2007 by Armin Ronacher.
     :license: BSD, see LICENSE for more details.
 """
+from py.test import raises
 from jinja2 import Environment
 from jinja2.sandbox import SandboxedEnvironment, \
      ImmutableSandboxedEnvironment, unsafe
 from jinja2 import Markup, escape
+from jinja2.exceptions import SecurityError
 
 
 class PrivateStuff(object):
@@ -132,3 +134,9 @@ def test_template_data():
     assert escape(t.module) == escaped_out
     assert t.module.say_hello('<blink>foo</blink>') == escaped_out
     assert escape(t.module.say_hello('<blink>foo</blink>')) == escaped_out
+
+
+def test_attr_filter():
+    env = SandboxedEnvironment()
+    tmpl = env.from_string('{{ 42|attr("__class__")|attr("__subclasses__")() }}')
+    raises(SecurityError, tmpl.render)
index 7a3882e6358ead9a7b23850936dc75ea9abccc58..cbde4db49ba96cde0f4950b713d0ba4f4b3bf198 100644 (file)
@@ -68,3 +68,5 @@ def test_item_before_attribute():
         tmpl = env.from_string('{{ foo.items() }}')
         assert tmpl.render(foo={'items': lambda: 42}) == '42'
         assert tmpl.render(foo={}) == '[]'
+        tmpl = env.from_string('{{ foo|attr("items")() }}')
+        assert tmpl.render(foo={'items': None}) == "[('items', None)]"