From 77f43366d3a7e0e3b998e3edee6718af544b5487 Mon Sep 17 00:00:00 2001
From: Calum Hutton <calum.hutton@snyk.io>
Date: Thu, 26 Oct 2023 12:08:53 +0100
Subject: xmlattr filter disallows keys with spaces

---
 src/jinja2/filters.py | 25 ++++++++++++++++++-------
 tests/test_filters.py |  6 ++++++
 2 files changed, 24 insertions(+), 7 deletions(-)

diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py
index ed07c4c..3e07526 100644
--- a/src/jinja2/filters.py
+++ b/src/jinja2/filters.py
@@ -248,13 +248,17 @@ def do_items(value: t.Union[t.Mapping[K, V], Undefined]) -> t.Iterator[t.Tuple[K
     yield from value.items()
 
 
+_space_re = re.compile(r"\s", flags=re.ASCII)
+
+
 @pass_eval_context
 def do_xmlattr(
     eval_ctx: "EvalContext", d: t.Mapping[str, t.Any], autospace: bool = True
 ) -> str:
     """Create an SGML/XML attribute string based on the items in a dict.
-    All values that are neither `none` nor `undefined` are automatically
-    escaped:
+
+    If any key contains a space, this fails with a ``ValueError``. Values that
+    are neither ``none`` nor ``undefined`` are automatically escaped.
 
     .. sourcecode:: html+jinja
 
@@ -274,11 +278,18 @@ def do_xmlattr(
     As you can see it automatically prepends a space in front of the item
     if the filter returned something unless the second parameter is false.
     """
-    rv = " ".join(
-        f'{escape(key)}="{escape(value)}"'
-        for key, value in d.items()
-        if value is not None and not isinstance(value, Undefined)
-    )
+    items = []
+
+    for key, value in d.items():
+        if value is None or isinstance(value, Undefined):
+            continue
+
+        if _space_re.search(key) is not None:
+            raise ValueError(f"Spaces are not allowed in attributes: '{key}'")
+
+        items.append(f'{escape(key)}="{escape(value)}"')
+
+    rv = " ".join(items)
 
     if autospace and rv:
         rv = " " + rv
diff --git a/tests/test_filters.py b/tests/test_filters.py
index 73f0f0b..a184649 100644
--- a/tests/test_filters.py
+++ b/tests/test_filters.py
@@ -474,6 +474,12 @@ class TestFilter:
         assert 'bar="23"' in out
         assert 'blub:blub="&lt;?&gt;"' in out
 
+    def test_xmlattr_key_with_spaces(self, env):
+        with pytest.raises(ValueError, match="Spaces are not allowed"):
+            env.from_string(
+                "{{ {'src=1 onerror=alert(1)': 'my_class'}|xmlattr }}"
+            ).render()
+
     def test_sort1(self, env):
         tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}")
         assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]"
-- 
2.30.2

