jinja icon indicating copy to clipboard operation
jinja copied to clipboard

Block assignment with filter and autoescape

Open juriad opened this issue 5 years ago • 1 comments

In the following code the result depends on atoescaping even though there are no characters which need escaping. Am I understanding atoescaping incorrectly? If you set autoescape=False it works as expected.

There is a workaround (inspired by https://github.com/pallets/jinja/issues/486). I consider it a bug as it is exactly what the OP of https://github.com/pallets/jinja/issues/486 wanted to achieve from filtering.

The goal is to process (with a filter) whatever is inside the block assignment and store it to a variable. The filter is responsible for parsing the content and coercing it to a correct type (bool, number, string, lists). Note that the content of the block assignment can contain any Jinja instructions.

Expected Behavior

Prints POSTGRES

Actual Behavior

Prints ORACLE

Template Code

from jinja2 import Environment, FileSystemLoader

env = Environment(loader=FileSystemLoader("."), autoescape=True)
env.filters['parse_bool'] = lambda s: s == 'True'

# BUG
template = env.from_string("""
{% set ORACLE | parse_bool %}False{% endset %}
{{ 'ORACLE' if ORACLE else 'POSTGRES' }}
""")
print(template.render())

# WORKAROUND
template = env.from_string("""
{% set ORACLE %}False{% endset %}{% set ORACLE = ORACLE | parse_bool %}
{{ 'ORACLE' if ORACLE else 'POSTGRES' }}
""")
print(template.render())

Your Environment

  • Python version: 3.8
  • Jinja version: 2.11.2

juriad avatar Apr 24 '20 17:04 juriad

I dug into what exactly is happening there, this is the code that jinja generates for the buggy template:
(I stripped some whitespace, my template looks like this: "{% set ORACLE | parse_bool %}False{% endset %}{{ 'ORACLE' if ORACLE else 'POSTGRES' }}")

from __future__ import generator_stop
from jinja2.runtime import LoopContext, TemplateReference, Macro, Markup, TemplateRuntimeError, missing, concat, escape, markup_join, str_join, identity, TemplateNotFound, Namespace, Undefined
name = 'n'

def root(context, missing=missing, environment=environment):
    resolve = context.resolve_or_missing
    undefined = environment.undefined
    cond_expr_undefined = Undefined
    if 0: yield None
    l_0_ORACLE = missing
    t_1 = environment.filters['parse_bool']
    pass
    t_2 = []
    pass
    t_2.append(
        'False',
    )
    l_0_ORACLE = (Markup if context.eval_ctx.autoescape else identity)(t_1(Markup(concat(t_2)))) # the issue happens here
    context.vars['ORACLE'] = l_0_ORACLE
    context.exported_vars.add('ORACLE')
    yield escape(('ORACLE' if (undefined(name='ORACLE') if l_0_ORACLE is missing else l_0_ORACLE) else 'POSTGRES'))

blocks = {}
debug_info = '1=18'

The issue is the quite obvious - the "False" string is converted to markup, then to False by the filter, but then back to markup because autoescape is True. This points to that this is related to #490 . The markup calls are inserted by compiler here in visit_Filter and here in visit_AssignBlock

This needs to be solved by someone with deeper understanding of the Safe/Unsafe interactions in jinja, i.e. not me.

mvolfik avatar Mar 08 '21 10:03 mvolfik