added autoescaping
authorArmin Ronacher <armin.ronacher@active-4.com>
Thu, 17 Apr 2008 08:03:29 +0000 (10:03 +0200)
committerArmin Ronacher <armin.ronacher@active-4.com>
Thu, 17 Apr 2008 08:03:29 +0000 (10:03 +0200)
--HG--
branch : trunk

jinja2/__init__.py
jinja2/compiler.py
jinja2/debug.py
jinja2/environment.py
jinja2/filters.py
jinja2/runtime.py
jinja2/utils.py

index dcebefcae2e45512415f31ffae261b161900d31a..b3507a36f4115c08f128e73a6b6bf1fb02edeabc 100644 (file)
@@ -58,3 +58,4 @@
 """
 from jinja2.environment import Environment
 from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined
+from jinja2.utils import Markup, escape
index d1025d4d126b8a998cabc0e3022aa2a4df605657..64524eda38f229cad6fb97e90570d70998e971be 100644 (file)
@@ -781,8 +781,8 @@ class CodeGenerator(NodeVisitor):
             finalizer = 'unicode'
             have_finalizer = False
         else:
-            finalizer = 'context.finalize'
-            have_finalizer = False
+            finalizer = 'environment.finalize'
+            have_finalizer = True
 
         # if we are in the toplevel scope and there was already an extends
         # statement we have to add a check that disables our yield(s) here
@@ -846,7 +846,7 @@ class CodeGenerator(NodeVisitor):
             for argument in arguments:
                 self.newline(argument)
                 if have_finalizer:
-                    self.write('(')
+                    self.write(finalizer + '(')
                 self.visit(argument, frame)
                 if have_finalizer:
                     self.write(')')
index ecd84ae42be523ae4d0306cbda9f704474e66373..1b558f04be66933a46804de43928b27cd8b7ede8 100644 (file)
@@ -9,7 +9,6 @@
     :license: BSD.
 """
 import sys
-from jinja2.exceptions import TemplateNotFound
 
 
 def translate_exception(exc_info):
@@ -44,6 +43,10 @@ def fake_exc_info(exc_info, filename, lineno, tb_back=None):
         if name.startswith('l_'):
             locals[name[2:]] = value
 
+    # if there is a local called __jinja_exception__, we get
+    # rid of it to not break the debug functionality.
+    locals.pop('__jinja_exception__', None)
+
     # assamble fake globals we need
     globals = {
         '__name__':             filename,
index d0bbb1d4c98efb585b12cd14eb53e69450cb48bd..9fdfdae89e907bb0c311bb0b4472e23b66c8853b 100644 (file)
@@ -42,7 +42,8 @@ class Environment(object):
                  trim_blocks=False,
                  optimized=True,
                  undefined=Undefined,
-                 loader=None):
+                 loader=None,
+                 finalize=unicode):
         """Here the possible initialization parameters:
 
         ========================= ============================================
@@ -88,18 +89,13 @@ class Environment(object):
         self.trim_blocks = trim_blocks
         self.undefined = undefined
         self.optimized = optimized
+        self.finalize = finalize
 
         # defaults
         self.filters = DEFAULT_FILTERS.copy()
         self.tests = DEFAULT_TESTS.copy()
         self.globals = DEFAULT_NAMESPACE.copy()
 
-        # if no finalize function/method exists we default to unicode.  The
-        # compiler check if the finalize attribute *is* unicode, if yes no
-        # finalizaion is written where it can be avoided.
-        if not hasattr(self, 'finalize'):
-            self.finalize = unicode
-
         # set the loader provided
         self.loader = loader
 
index 45e41375aa49a48e9806dc290aed4b08839c77a5..46a0e09dbf71babdd059d3d94d6207008ccfd533 100644 (file)
@@ -15,7 +15,7 @@ try:
 except ImportError:
     itemgetter = lambda a: lambda b: b[a]
 from urllib import urlencode, quote
-from jinja2.utils import escape, pformat, urlize
+from jinja2.utils import Markup, escape, pformat, urlize
 from jinja2.runtime import Undefined
 
 
@@ -711,5 +711,6 @@ FILTERS = {
     'abs':                  abs,
     'round':                do_round,
     'sort':                 do_sort,
-    'groupby':              do_groupby
+    'groupby':              do_groupby,
+    'safe':                 Markup
 }
index 3e3721da91c55e293cb25a960c7d624739091d5c..247958f590085c7583b658f6130081ef9c85fe32 100644 (file)
@@ -12,19 +12,19 @@ try:
     from collections import defaultdict
 except ImportError:
     defaultdict = None
+from jinja2.utils import Markup
 
 
 __all__ = ['LoopContext', 'StaticLoopContext', 'TemplateContext',
            'Macro', 'IncludedTemplate', 'TemplateData']
 
 
-class TemplateData(unicode):
+class TemplateData(Markup):
     """Marks data as "coming from the template".  This is used to let the
     system know that this data is already processed if a finalization is
-    used."""
-
-    def __html__(self):
-        return self
+    used.
+    """
+    __slots__ = ()
 
 
 class TemplateContext(dict):
@@ -119,7 +119,10 @@ class IncludedTemplate(object):
         return self._context[name]
 
     def __unicode__(self):
-        return self._context
+        return self._rendered_body
+
+    def __html__(self):
+        return self._rendered_body
 
     def __repr__(self):
         return '<%s %r>' % (
index b597ed0586fef5a64692419ab6aa21dbf0446c56..af1066c74f22f67254772133a330f657bd17c7ae 100644 (file)
 """
 import re
 import string
+from functools import update_wrapper
+from itertools import imap
 
 
 def escape(obj, attribute=False):
     """HTML escape an object."""
     if hasattr(obj, '__html__'):
         return obj.__html__()
-    return unicode(obj) \
-        .replace('&', '&amp;') \
-        .replace('>', '&gt;') \
-        .replace('<', '&lt;') \
+    return Markup(unicode(obj)
+        .replace('&', '&amp;')
+        .replace('>', '&gt;')
+        .replace('<', '&lt;')
         .replace('"', '&quot;')
+    )
 
 
 def pformat(obj, verbose=False):
@@ -39,9 +42,9 @@ def pformat(obj, verbose=False):
 _word_split_re = re.compile(r'(\s+)')
 
 _punctuation_re = re.compile(
-    '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' %  (
-        '|'.join([re.escape(p) for p in ('(', '<', '&lt;')]),
-        '|'.join([re.escape(p) for p in ('.', ',', ')', '>', '\n', '&gt;')])
+    '^(?P<lead>(?:%s)*)(?P<middle>.*?)(?P<trail>(?:%s)*)$' % (
+        '|'.join(imap(re.escape, ('(', '<', '&lt;'))),
+        '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '&gt;')))
     )
 )
 
@@ -91,3 +94,94 @@ def urlize(text, trim_url_limit=None, nofollow=False):
             if lead + middle + trail != word:
                 words[i] = lead + middle + trail
     return u''.join(words)
+
+
+class Markup(unicode):
+    """Marks a string as being safe for inclusion in HTML/XML output without
+    needing to be escaped.  This implements the `__html__` interface a couple
+    of frameworks and web applications use.
+
+    The `escape` function returns markup objects so that double escaping can't
+    happen.  If you want to use autoescaping in Jinja just set the finalizer
+    of the environment to `escape`.
+    """
+
+    __slots__ = ()
+
+    def __html__(self):
+        return self
+
+    def __add__(self, other):
+        if hasattr(other, '__html__') or isinstance(other, basestring):
+            return self.__class__(unicode(self) + unicode(escape(other)))
+        return NotImplemented
+
+    def __radd__(self, other):
+        if hasattr(other, '__html__') or isinstance(other, basestring):
+            return self.__class__(unicode(escape(other)) + unicode(self))
+        return NotImplemented
+
+    def __mul__(self, num):
+        if not isinstance(num, (int, long)):
+            return NotImplemented
+        return self.__class__(unicode.__mul__(self, num))
+    __rmul__ = __mul__
+
+    def __mod__(self, arg):
+        if isinstance(arg, tuple):
+            arg = tuple(imap(_MarkupEscapeHelper, arg))
+        else:
+            arg = _MarkupEscapeHelper(arg)
+        return self.__class__(unicode.__mod__(self, arg))
+
+    def __repr__(self):
+        return '%s(%s)' % (
+            self.__class__.__name__,
+            unicode.__repr__(self)
+        )
+
+    def join(self, seq):
+        return self.__class__(unicode.join(self, imap(escape, seq)))
+
+    def split(self, *args, **kwargs):
+        return map(self.__class__, unicode.split(self, *args, **kwargs))
+
+    def rsplit(self, *args, **kwargs):
+        return map(self.__class__, unicode.rsplit(self, *args, **kwargs))
+
+    def splitlines(self, *args, **kwargs):
+        return map(self.__class__, unicode.splitlines(self, *args, **kwargs))
+
+    def make_wrapper(name):
+        orig = getattr(unicode, name)
+        def func(self, *args, **kwargs):
+            args = list(args)
+            for idx, arg in enumerate(args):
+                if hasattr(arg, '__html__') or isinstance(arg, basestring):
+                    args[idx] = escape(arg)
+            for name, arg in kwargs.iteritems():
+                if hasattr(arg, '__html__') or isinstance(arg, basestring):
+                    kwargs[name] = escape(arg)
+            return self.__class__(orig(self, *args, **kwargs))
+        return update_wrapper(func, orig, ('__name__', '__doc__'))
+    for method in '__getitem__', '__getslice__', 'capitalize', \
+                  'title', 'lower', 'upper', 'replace', 'ljust', \
+                  'rjust', 'lstrip', 'rstrip', 'partition', 'center', \
+                  'strip', 'translate', 'expandtabs', 'rpartition', \
+                  'swapcase', 'zfill':
+        locals()[method] = make_wrapper(method)
+    del method, make_wrapper
+
+
+class _MarkupEscapeHelper(object):
+    """Helper for Markup.__mod__"""
+
+    def __init__(self, obj):
+        self.obj = obj
+
+    __getitem__ = lambda s, x: _MarkupEscapeHelper(s.obj[x])
+    __unicode__ = lambda s: unicode(escape(s.obj))
+    __str__ = lambda s: str(escape(s.obj))
+    __repr__ = lambda s: str(repr(escape(s.obj)))
+    __int__ = lambda s: int(s.obj)
+    __float__ = lambda s: float(s.obj)