- Depend on MarkupSafe 0.23 or higher.
- Improved the `truncate` filter to support better truncation in case
the string is barely truncated at all.
+- Change the logic for macro autoescaping to be based on the runtime
+ autoescaping information at call time instead of macro define time.
Version 2.8.2
-------------
frame.buffer = self.temporary_identifier()
self.writeline('%s = []' % frame.buffer)
- def return_buffer_contents(self, frame):
+ def return_buffer_contents(self, frame, force_unescaped=False):
"""Return the buffer contents of the frame."""
- if frame.eval_ctx.volatile:
- self.writeline('if context.eval_ctx.autoescape:')
- self.indent()
- self.writeline('return Markup(concat(%s))' % frame.buffer)
- self.outdent()
- self.writeline('else:')
- self.indent()
- self.writeline('return concat(%s)' % frame.buffer)
- self.outdent()
- elif frame.eval_ctx.autoescape:
- self.writeline('return Markup(concat(%s))' % frame.buffer)
- else:
- self.writeline('return concat(%s)' % frame.buffer)
+ if not force_unescaped:
+ if frame.eval_ctx.volatile:
+ self.writeline('if context.eval_ctx.autoescape:')
+ self.indent()
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ self.outdent()
+ self.writeline('else:')
+ self.indent()
+ self.writeline('return concat(%s)' % frame.buffer)
+ self.outdent()
+ return
+ elif frame.eval_ctx.autoescape:
+ self.writeline('return Markup(concat(%s))' % frame.buffer)
+ return
+ self.writeline('return concat(%s)' % frame.buffer)
def indent(self):
"""Indent by one."""
self.outdent()
self.blockvisit(node.body, frame)
- self.return_buffer_contents(frame)
+ self.return_buffer_contents(frame, force_unescaped=True)
self.leave_frame(frame, with_python_scope=True)
self.outdent()
name = getattr(macro_ref.node, 'name', None)
if len(macro_ref.node.args) == 1:
arg_tuple += ','
- self.write('Macro(environment, macro, %r, (%s), %r, %r, %r)' %
+ self.write('Macro(environment, macro, %r, (%s), %r, %r, %r, '
+ 'context.eval_ctx.autoescape)' %
(name, arg_tuple, macro_ref.accesses_kwargs,
macro_ref.accesses_varargs, macro_ref.accesses_caller))
from itertools import chain
from jinja2.nodes import EvalContext, _context_function_types
from jinja2.utils import Markup, soft_unicode, escape, missing, concat, \
- internalcode, object_type_repr
+ internalcode, object_type_repr, evalcontextfunction
from jinja2.exceptions import UndefinedError, TemplateRuntimeError, \
TemplateNotFound
from jinja2._compat import imap, text_type, iteritems, \
"""Wraps a macro function."""
def __init__(self, environment, func, name, arguments,
- catch_kwargs, catch_varargs, caller):
+ catch_kwargs, catch_varargs, caller,
+ default_autoescape=None):
self._environment = environment
self._func = func
self._argument_count = len(arguments)
self.catch_kwargs = catch_kwargs
self.catch_varargs = catch_varargs
self.caller = caller
+ if default_autoescape is None:
+ default_autoescape = environment.autoescape
+ self._default_autoescape = default_autoescape
@internalcode
+ @evalcontextfunction
def __call__(self, *args, **kwargs):
+ # This requires a bit of explanation, In the past we used to
+ # decide largely based on compile-time information if a macro is
+ # safe or unsafe. While there was a volatile mode it was largely
+ # unused for deciding on escaping. This turns out to be
+ # problemtic for macros because if a macro is safe or not not so
+ # much depends on the escape mode when it was defined but when it
+ # was used.
+ #
+ # Because however we export macros from the module system and
+ # there are historic callers that do not pass an eval context (and
+ # will continue to not pass one), we need to perform an instance
+ # check here.
+ #
+ # This is considered safe because an eval context is not a valid
+ # argument to callables otherwise anwyays. Worst case here is
+ # that if no eval context is passed we fall back to the compile
+ # time autoescape flag.
+ if args and isinstance(args[0], EvalContext):
+ autoescape = args[0].autoescape
+ args = args[1:]
+ else:
+ autoescape = self._default_autoescape
+
# try to consume the positional arguments
arguments = list(args[:self._argument_count])
off = len(arguments)
elif len(args) > self._argument_count:
raise TypeError('macro %r takes not more than %d argument(s)' %
(self.name, len(self.arguments)))
- return self._func(*arguments)
+
+ rv = self._func(*arguments)
+ if autoescape:
+ rv = Markup(rv)
+ return rv
def __repr__(self):
return '<%s %s>' % (
assert t.render(ae=True) == '<strong>Wert: <test></strong>'
assert t.render(ae=False) == '<strong>Wert: <test></strong>'
+ def test_autoescape_macros(self):
+ env = Environment(autoescape=False, extensions=['jinja2.ext.autoescape'])
+ template = (
+ '{% macro m() %}<html>{% endmacro %}'
+ '{% autoescape true %}{{ m() }}{% endautoescape %}'
+ )
+ assert env.from_string(template).render() == '<html>'
+
def test_num_used_twice(self):
tmpl = newstyle_i18n_env.get_template('ngettext_long.html')
assert tmpl.render(apples=5, LANGUAGE='de') == u'5 Äpfel'
from jinja2 import Markup, escape
from jinja2.exceptions import SecurityError, TemplateSyntaxError, \
TemplateRuntimeError
+from jinja2.nodes import EvalContext
from jinja2._compat import text_type
assert text_type(t.module) == escaped_out
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
+ assert escape(t.module.say_hello(
+ EvalContext(env), '<blink>foo</blink>')) == escaped_out
+ assert escape(t.module.say_hello(
+ '<blink>foo</blink>')) == escaped_out
def test_attr_filter(self, env):
env = SandboxedEnvironment()