From 9f30bc8c4d6702a2e206fd8027443d2edafe4729 Mon Sep 17 00:00:00 2001 From: Ayala Shachar Date: Tue, 23 May 2017 10:24:52 -0700 Subject: [PATCH 1/9] Make tojson always safe (fix #709) --- jinja2/utils.py | 2 +- tests/test_filters.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/jinja2/utils.py b/jinja2/utils.py index b96d30954..40c87ff4f 100644 --- a/jinja2/utils.py +++ b/jinja2/utils.py @@ -567,7 +567,7 @@ def htmlsafe_json_dumps(obj, dumper=None, **kwargs): .replace(u'>', u'\\u003e') \ .replace(u'&', u'\\u0026') \ .replace(u"'", u'\\u0027') - return rv + return Markup(rv) @implements_iterator diff --git a/tests/test_filters.py b/tests/test_filters.py index 318a347c4..ff941832d 100644 --- a/tests/test_filters.py +++ b/tests/test_filters.py @@ -580,8 +580,9 @@ def __init__(self, id, name): def test_json_dump(self): env = Environment(autoescape=True) t = env.from_string('{{ x|tojson }}') - assert t.render(x={'foo': 'bar'}) == '{"foo": "bar"}' - assert t.render(x='"bar\'') == r'"\"bar\u0027"' + assert t.render(x={'foo': 'bar'}) == '{"foo": "bar"}' + assert t.render(x='"ba&r\'') == r'"\"ba\u0026r\u0027"' + assert t.render(x='') == r'"\u003cbar\u003e"' def my_dumps(value, **options): assert options == {'foo': 'bar'} From 86346976c61049b44d59448c12688a5cc3646917 Mon Sep 17 00:00:00 2001 From: David Lord Date: Tue, 23 May 2017 14:57:34 -0700 Subject: [PATCH 2/9] add changelog [ci skip] --- CHANGES | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES b/CHANGES index 967b40aa2..5c13ee793 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,16 @@ Jinja2 Changelog ================ +Version 2.9.7 +------------- + +(bugfix release, in development) + +- ``tojson`` filter marks output as safe to match documented behavior. + (`#718`_) + +.. _#718: https://github.com/pallets/jinja/pull/718 + Version 2.9.6 ------------- (bugfix release, released on April 3rd 2017) From 60a7e775d487c8de9f1b0f23c91ca9845c161fc2 Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Tue, 8 Aug 2017 15:00:20 -0700 Subject: [PATCH 3/9] Fix regression in 2.9 involving unsafe Context.get_all() usage Since commit d67f0fd4cc2a4af08f51f4466150d49da7798729, callers of Context.get_all() need to make a copy it they're going to modify the result. Fixes: d67f0fd4cc2a ("Generalize scoping. This fixes #603") --- jinja2/debug.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jinja2/debug.py b/jinja2/debug.py index 07c21f1a8..b61139f0c 100644 --- a/jinja2/debug.py +++ b/jinja2/debug.py @@ -198,7 +198,7 @@ def translate_exception(exc_info, initial_skip=0): def get_jinja_locals(real_locals): ctx = real_locals.get('context') if ctx: - locals = ctx.get_all() + locals = ctx.get_all().copy() else: locals = {} From d117425f5ed3f542100f20d3bf700ae7bc54039f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 9 Aug 2017 09:06:39 +0200 Subject: [PATCH 4/9] Added a changelog entry --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 5c13ee793..38f4508cc 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ Version 2.9.7 - ``tojson`` filter marks output as safe to match documented behavior. (`#718`_) +- Resolved a bug where getting debug locals for tracebacks could + modify template context. .. _#718: https://github.com/pallets/jinja/pull/718 From cde2a54b87876bd02b283db2f52aca3d9272a92c Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Tue, 22 Aug 2017 22:59:57 +0200 Subject: [PATCH 5/9] Compile `elif` tag to `elif` instead of `else: if` This avoids deep nesting in case of many `{% elif .. %}` blocks (which would fail during execution) and also deep recursion (which may fail during compilation) fixes #759 --- CHANGES | 3 +++ jinja2/compiler.py | 7 +++++++ jinja2/idtracking.py | 3 ++- jinja2/nodes.py | 2 +- jinja2/parser.py | 13 ++++++------- tests/test_core_tags.py | 9 +++++++++ tests/test_idtracking.py | 8 ++++---- 7 files changed, 32 insertions(+), 13 deletions(-) diff --git a/CHANGES b/CHANGES index 38f4508cc..7e5df3dd5 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,9 @@ Version 2.9.7 (`#718`_) - Resolved a bug where getting debug locals for tracebacks could modify template context. +- Fixed a bug where having many `{% elif ... %}` blocks resulted in a + "too many levels of indentation" error. These blocks now compile to + native `elif ..:` instead of `else: if ..:` (#759) .. _#718: https://github.com/pallets/jinja/pull/718 diff --git a/jinja2/compiler.py b/jinja2/compiler.py index b2ab6fe6a..30826f340 100644 --- a/jinja2/compiler.py +++ b/jinja2/compiler.py @@ -1129,6 +1129,13 @@ def visit_If(self, node, frame): self.indent() self.blockvisit(node.body, if_frame) self.outdent() + for elif_ in node.elif_: + self.writeline('elif ', elif_) + self.visit(elif_.test, if_frame) + self.write(':') + self.indent() + self.blockvisit(elif_.body, if_frame) + self.outdent() if node.else_: self.writeline('else:') self.indent() diff --git a/jinja2/idtracking.py b/jinja2/idtracking.py index 8479b72c2..30e348de4 100644 --- a/jinja2/idtracking.py +++ b/jinja2/idtracking.py @@ -222,9 +222,10 @@ def inner_visit(nodes): return rv body_symbols = inner_visit(node.body) + elif_symbols = inner_visit(node.elif_) else_symbols = inner_visit(node.else_ or ()) - self.symbols.branch_update([body_symbols, else_symbols]) + self.symbols.branch_update([body_symbols, elif_symbols, else_symbols]) def visit_Macro(self, node, **kwargs): self.symbols.store(node.name) diff --git a/jinja2/nodes.py b/jinja2/nodes.py index aa4df7238..59e076512 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -314,7 +314,7 @@ class For(Stmt): class If(Stmt): """If `test` is true, `body` is rendered, else `else_`.""" - fields = ('test', 'body', 'else_') + fields = ('test', 'body', 'elif_', 'else_') class Macro(Stmt): diff --git a/jinja2/parser.py b/jinja2/parser.py index 0bf74c945..70ab7ca77 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -210,17 +210,16 @@ def parse_if(self): node.test = self.parse_tuple(with_condexpr=False) node.body = self.parse_statements(('name:elif', 'name:else', 'name:endif')) + node.elif_ = [] + node.else_ = [] token = next(self.stream) if token.test('name:elif'): - new_node = nodes.If(lineno=self.stream.current.lineno) - node.else_ = [new_node] - node = new_node + node = nodes.If(lineno=self.stream.current.lineno) + result.elif_.append(node) continue elif token.test('name:else'): - node.else_ = self.parse_statements(('name:endif',), - drop_needle=True) - else: - node.else_ = [] + result.else_ = self.parse_statements(('name:endif',), + drop_needle=True) break return result diff --git a/tests/test_core_tags.py b/tests/test_core_tags.py index f48d8b448..19b849e2f 100644 --- a/tests/test_core_tags.py +++ b/tests/test_core_tags.py @@ -222,6 +222,15 @@ def test_elif(self, env): %}...{% else %}XXX{% endif %}''') assert tmpl.render() == '...' + def test_elif_deep(self, env): + elifs = '\n'.join('{{% elif a == {0} %}}{0}'.format(i) + for i in range(1, 1000)) + tmpl = env.from_string('{{% if a == 0 %}}0{0}{{% else %}}x{{% endif %}}' + .format(elifs)) + for x in (0, 10, 999): + assert tmpl.render(a=x).strip() == str(x) + assert tmpl.render(a=1000).strip() == 'x' + def test_else(self, env): tmpl = env.from_string('{% if false %}XXX{% else %}...{% endif %}') assert tmpl.render() == '...' diff --git a/tests/test_idtracking.py b/tests/test_idtracking.py index ea01b17ef..29312d3e3 100644 --- a/tests/test_idtracking.py +++ b/tests/test_idtracking.py @@ -61,7 +61,7 @@ def test_complex(): nodes.Output([ nodes.Name('title_upper', 'load'), nodes.Call(nodes.Name('render_title', 'load'), [ - nodes.Const('Aha')], [], None, None)])], [])])]) + nodes.Const('Aha')], [], None, None)])], [], [])])]) for_loop = nodes.For( nodes.Name('item', 'store'), @@ -155,7 +155,7 @@ def test_if_branching_stores(): tmpl = nodes.Template([ nodes.If(nodes.Name('expression', 'load'), [ nodes.Assign(nodes.Name('variable', 'store'), - nodes.Const(42))], [])]) + nodes.Const(42))], [], [])]) sym = symbols_for_node(tmpl) assert sym.refs == { @@ -177,7 +177,7 @@ def test_if_branching_stores_undefined(): nodes.Assign(nodes.Name('variable', 'store'), nodes.Const(23)), nodes.If(nodes.Name('expression', 'load'), [ nodes.Assign(nodes.Name('variable', 'store'), - nodes.Const(42))], [])]) + nodes.Const(42))], [], [])]) sym = symbols_for_node(tmpl) assert sym.refs == { @@ -197,7 +197,7 @@ def test_if_branching_stores_undefined(): def test_if_branching_multi_scope(): for_loop = nodes.For(nodes.Name('item', 'store'), nodes.Name('seq', 'load'), [ nodes.If(nodes.Name('expression', 'load'), [ - nodes.Assign(nodes.Name('x', 'store'), nodes.Const(42))], []), + nodes.Assign(nodes.Name('x', 'store'), nodes.Const(42))], [], []), nodes.Include(nodes.Const('helper.html'), True, False) ], [], None, False) From fc5be479ddb3ffd2687607aada5a53bd5304f060 Mon Sep 17 00:00:00 2001 From: Adrian Moennich Date: Tue, 22 Aug 2017 23:24:52 +0200 Subject: [PATCH 6/9] s/Add/Added/ in CHANGES Pretty much all other entries also use past tense --- CHANGES | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 7256cf05c..21a60ecfa 100644 --- a/CHANGES +++ b/CHANGES @@ -24,13 +24,13 @@ Version 2.10 `trans` blocks. - The ``random`` filter is no longer incorrectly constant folded and will produce a new random choice each time the template is rendered. (`#478`_) -- Add a ``unique`` filter. (`#469`_) -- Add ``min`` and ``max`` filters. (`#475`_) -- Add tests for all comparison operators: ``eq``, ``ne``, ``lt``, ``le``, +- Added a ``unique`` filter. (`#469`_) +- Added ``min`` and ``max`` filters. (`#475`_) +- Added tests for all comparison operators: ``eq``, ``ne``, ``lt``, ``le``, ``gt``, ``ge``. (`#665`_) - ``import`` statement cannot end with a trailing comma. (`#617`_, `#618`_) - ``indent`` filter will not indent blank lines by default. (`#685`_) -- Add ``reverse`` argument for ``dictsort`` filter. (`#692`_) +- Added ``reverse`` argument for ``dictsort`` filter. (`#692`_) .. _#469: https://github.com/pallets/jinja/pull/469 .. _#475: https://github.com/pallets/jinja/pull/475 From 4750cf7c69c0ce2a0c44cc68c2fc06b984774e5e Mon Sep 17 00:00:00 2001 From: Eli Boyarski Date: Mon, 18 Sep 2017 17:24:38 +0300 Subject: [PATCH 7/9] Minor docstring grammar fix (#772) --- jinja2/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jinja2/environment.py b/jinja2/environment.py index 2a4d3d7da..549d9afab 100644 --- a/jinja2/environment.py +++ b/jinja2/environment.py @@ -809,7 +809,7 @@ def _load_template(self, name, globals): @internalcode def get_template(self, name, parent=None, globals=None): """Load a template from the loader. If a loader is configured this - method ask the loader for the template and returns a :class:`Template`. + method asks the loader for the template and returns a :class:`Template`. If the `parent` parameter is not `None`, :meth:`join_path` is called to get the real template name before loading. From c3147611ec6bbfd8b6e42637c73a68e29de1e2e7 Mon Sep 17 00:00:00 2001 From: David Lord Date: Sun, 8 Oct 2017 13:15:09 -0700 Subject: [PATCH 8/9] codecov needs argparse on 2.6 --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 908182016..406eaebac 100644 --- a/tox.ini +++ b/tox.ini @@ -34,6 +34,8 @@ passenv = CI TRAVIS TRAVIS_* deps = codecov skip_install = true commands = + # install argparse for 2.6 + python -c 'import sys, pip; sys.version_info < (2, 7) and pip.main(["install", "argparse", "-q"])' coverage combine coverage report codecov From 31f92b5947e9dba8ba997dd93921cbf1538bb79f Mon Sep 17 00:00:00 2001 From: Ricardo Lafuente Date: Mon, 16 Oct 2017 12:11:44 +0100 Subject: [PATCH 9/9] Fix typo in docstring (#779) --- jinja2/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jinja2/filters.py b/jinja2/filters.py index 07e8ccae8..267ddddaa 100644 --- a/jinja2/filters.py +++ b/jinja2/filters.py @@ -339,7 +339,7 @@ def do_min(environment, value, case_sensitive=False, attribute=None): @environmentfilter def do_max(environment, value, case_sensitive=False, attribute=None): - """Return the smallest item from the sequence. + """Return the largest item from the sequence. .. sourcecode:: jinja