Skip to content

Commit 3fd91e4

Browse files
committed
Merge branch '3.1.x'
2 parents 7f8fb54 + 36f9885 commit 3fd91e4

File tree

9 files changed

+69
-26
lines changed

9 files changed

+69
-26
lines changed

.github/workflows/publish.yaml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,10 @@ jobs:
5858
# files in the draft release.
5959
environment: 'publish'
6060
runs-on: ubuntu-latest
61+
permissions:
62+
id-token: write
6163
steps:
6264
- uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a
63-
# Try uploading to Test PyPI first, in case something fails.
64-
- uses: pypa/gh-action-pypi-publish@b7f401de30cb6434a1e19f805ff006643653240e
65-
with:
66-
repository_url: https://test.pypi.org/legacy/
67-
packages_dir: artifact/
68-
- uses: pypa/gh-action-pypi-publish@b7f401de30cb6434a1e19f805ff006643653240e
65+
- uses: pypa/gh-action-pypi-publish@2f6f737ca5f74c637829c0f5c3acd0e29ea5e8bf
6966
with:
70-
packages_dir: artifact/
67+
packages-dir: artifact/

.pre-commit-config.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,21 @@ ci:
33
autoupdate_schedule: monthly
44
repos:
55
- repo: https://github.com/asottile/pyupgrade
6-
rev: v3.7.0
6+
rev: v3.15.0
77
hooks:
88
- id: pyupgrade
99
args: ["--py37-plus"]
1010
- repo: https://github.com/asottile/reorder-python-imports
11-
rev: v3.10.0
11+
rev: v3.12.0
1212
hooks:
1313
- id: reorder-python-imports
1414
args: ["--application-directories", "src"]
1515
- repo: https://github.com/psf/black
16-
rev: 23.3.0
16+
rev: 23.12.1
1717
hooks:
1818
- id: black
1919
- repo: https://github.com/PyCQA/flake8
20-
rev: 6.0.0
20+
rev: 7.0.0
2121
hooks:
2222
- id: flake8
2323
additional_dependencies: [flake8-bugbear]
@@ -26,7 +26,7 @@ repos:
2626
hooks:
2727
- id: pip-compile-multi-verify
2828
- repo: https://github.com/pre-commit/pre-commit-hooks
29-
rev: v4.4.0
29+
rev: v4.5.0
3030
hooks:
3131
- id: fix-byte-order-marker
3232
- id: trailing-whitespace

CHANGES.rst

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ Unreleased
1313
Version 3.1.3
1414
-------------
1515

16-
Unreleased
16+
Released 2024-01-10
1717

1818
- Fix compiler error when checking if required blocks in parent templates are
1919
empty. :pr:`1858`
20+
- ``xmlattr`` filter does not allow keys with spaces. GHSA-h5c8-rqwp-cp95
21+
- Make error messages stemming from invalid nesting of ``{% trans %}`` blocks
22+
more helpful. :pr:`1918`
2023

2124

2225
Version 3.1.2

src/jinja2/compiler.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,15 +1406,15 @@ def _make_finalize(self) -> _FinalizeInfo:
14061406

14071407
if pass_arg is None:
14081408

1409-
def finalize(value: t.Any) -> t.Any:
1409+
def finalize(value: t.Any) -> t.Any: # noqa: F811
14101410
return default(env_finalize(value))
14111411

14121412
else:
14131413
src = f"{src}{pass_arg}, "
14141414

14151415
if pass_arg == "environment":
14161416

1417-
def finalize(value: t.Any) -> t.Any:
1417+
def finalize(value: t.Any) -> t.Any: # noqa: F811
14181418
return default(env_finalize(self.environment, value))
14191419

14201420
self._finalize = self._FinalizeInfo(finalize, src)

src/jinja2/ext.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -495,16 +495,26 @@ def _parse_block(
495495
parser.stream.expect("variable_end")
496496
elif parser.stream.current.type == "block_begin":
497497
next(parser.stream)
498-
if parser.stream.current.test("name:endtrans"):
498+
block_name = (
499+
parser.stream.current.value
500+
if parser.stream.current.type == "name"
501+
else None
502+
)
503+
if block_name == "endtrans":
499504
break
500-
elif parser.stream.current.test("name:pluralize"):
505+
elif block_name == "pluralize":
501506
if allow_pluralize:
502507
break
503508
parser.fail(
504509
"a translatable section can have only one pluralize section"
505510
)
511+
elif block_name == "trans":
512+
parser.fail(
513+
"trans blocks can't be nested; did you mean `endtrans`?"
514+
)
506515
parser.fail(
507-
"control structures in translatable sections are not allowed"
516+
f"control structures in translatable sections are not allowed; "
517+
f"saw `{block_name}`"
508518
)
509519
elif parser.stream.eos:
510520
parser.fail("unclosed translation block")

src/jinja2/filters.py

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -248,13 +248,17 @@ def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K
248248
yield from value.items()
249249

250250

251+
_space_re = re.compile(r"\s", flags=re.ASCII)
252+
253+
251254
@pass_eval_context
252255
def do_xmlattr(
253256
eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True
254257
) -> str:
255258
"""Create an SGML/XML attribute string based on the items in a dict.
256-
All values that are neither `none` nor `undefined` are automatically
257-
escaped:
259+
260+
If any key contains a space, this fails with a ``ValueError``. Values that
261+
are neither ``none`` nor ``undefined`` are automatically escaped.
258262
259263
.. sourcecode:: html+jinja
260264
@@ -273,12 +277,22 @@ def do_xmlattr(
273277
274278
As you can see it automatically prepends a space in front of the item
275279
if the filter returned something unless the second parameter is false.
280+
281+
.. versionchanged:: 3.1.3
282+
Keys with spaces are not allowed.
276283
"""
277-
rv = " ".join(
278-
f'{escape(key)}="{escape(value)}"'
279-
for key, value in d.items()
280-
if value is not None and not isinstance(value, Undefined)
281-
)
284+
items = []
285+
286+
for key, value in d.items():
287+
if value is None or isinstance(value, Undefined):
288+
continue
289+
290+
if _space_re.search(key) is not None:
291+
raise ValueError(f"Spaces are not allowed in attributes: '{key}'")
292+
293+
items.append(f'{escape(key)}="{escape(value)}"')
294+
295+
rv = " ".join(items)
282296

283297
if autospace and rv:
284298
rv = " " + rv

src/jinja2/parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -859,7 +859,7 @@ def parse_subscribed(self) -> nodes.Expr:
859859
else:
860860
args.append(None)
861861

862-
return nodes.Slice(lineno=lineno, *args)
862+
return nodes.Slice(lineno=lineno, *args) # noqa: B026
863863

864864
def parse_call_args(
865865
self,

tests/test_ext.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from jinja2 import Environment
88
from jinja2 import nodes
99
from jinja2 import pass_context
10+
from jinja2 import TemplateSyntaxError
1011
from jinja2.exceptions import TemplateAssertionError
1112
from jinja2.ext import Extension
1213
from jinja2.lexer import count_newlines
@@ -468,6 +469,18 @@ def test_extract_context(self):
468469
(3, "npgettext", ("babel", "%(users)s user", "%(users)s users", None), []),
469470
]
470471

472+
def test_nested_trans_error(self):
473+
s = "{% trans %}foo{% trans %}{% endtrans %}"
474+
with pytest.raises(TemplateSyntaxError) as excinfo:
475+
i18n_env.from_string(s)
476+
assert "trans blocks can't be nested" in str(excinfo.value)
477+
478+
def test_trans_block_error(self):
479+
s = "{% trans %}foo{% wibble bar %}{% endwibble %}{% endtrans %}"
480+
with pytest.raises(TemplateSyntaxError) as excinfo:
481+
i18n_env.from_string(s)
482+
assert "saw `wibble`" in str(excinfo.value)
483+
471484

472485
class TestScope:
473486
def test_basic_scope_behavior(self):

tests/test_filters.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,12 @@ def test_xmlattr(self, env):
474474
assert 'bar="23"' in out
475475
assert 'blub:blub="<?>"' in out
476476

477+
def test_xmlattr_key_with_spaces(self, env):
478+
with pytest.raises(ValueError, match="Spaces are not allowed"):
479+
env.from_string(
480+
"{{ {'src=1 onerror=alert(1)': 'my_class'}|xmlattr }}"
481+
).render()
482+
477483
def test_sort1(self, env):
478484
tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}")
479485
assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]"

0 commit comments

Comments
 (0)