From 18c6ca0e4d30da530bf482673899fddda275de2a Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 17 Apr 2008 10:03:29 +0200 Subject: [PATCH] added autoescaping --HG-- branch : trunk --- jinja2/__init__.py | 1 + jinja2/compiler.py | 6 +-- jinja2/debug.py | 5 +- jinja2/environment.py | 10 ++-- jinja2/filters.py | 5 +- jinja2/runtime.py | 15 +++--- jinja2/utils.py | 108 +++++++++++++++++++++++++++++++++++++++--- 7 files changed, 124 insertions(+), 26 deletions(-) diff --git a/jinja2/__init__.py b/jinja2/__init__.py index dcebefc..b3507a3 100644 --- a/jinja2/__init__.py +++ b/jinja2/__init__.py @@ -58,3 +58,4 @@ """ from jinja2.environment import Environment from jinja2.runtime import Undefined, DebugUndefined, StrictUndefined +from jinja2.utils import Markup, escape diff --git a/jinja2/compiler.py b/jinja2/compiler.py index d1025d4..64524ed 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -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(')') diff --git a/jinja2/debug.py b/jinja2/debug.py index ecd84ae..1b558f0 100644 --- a/jinja2/debug.py +++ b/jinja2/debug.py @@ -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, diff --git a/jinja2/environment.py b/jinja2/environment.py index d0bbb1d..9fdfdae 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -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 diff --git a/jinja2/filters.py b/jinja2/filters.py index 45e4137..46a0e09 100644 --- a/jinja2/filters.py +++ b/jinja2/filters.py @@ -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 } diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 3e3721d..247958f 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -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>' % ( diff --git a/jinja2/utils.py b/jinja2/utils.py index b597ed0..af1066c 100644 --- a/jinja2/utils.py +++ b/jinja2/utils.py @@ -10,17 +10,20 @@ """ 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('&', '&') \ - .replace('>', '>') \ - .replace('<', '<') \ + return Markup(unicode(obj) + .replace('&', '&') + .replace('>', '>') + .replace('<', '<') .replace('"', '"') + ) 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(?:%s)*)(?P.*?)(?P(?:%s)*)$' % ( - '|'.join([re.escape(p) for p in ('(', '<', '<')]), - '|'.join([re.escape(p) for p in ('.', ',', ')', '>', '\n', '>')]) + '^(?P(?:%s)*)(?P.*?)(?P(?:%s)*)$' % ( + '|'.join(imap(re.escape, ('(', '<', '<'))), + '|'.join(imap(re.escape, ('.', ',', ')', '>', '\n', '>'))) ) ) @@ -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) -- 2.26.2