diff --git a/django/utils/feedgenerator.py b/django/utils/feedgenerator.py index a9eaffc205..1d7078ada0 100644 --- a/django/utils/feedgenerator.py +++ b/django/utils/feedgenerator.py @@ -29,6 +29,7 @@ from io import StringIO from urllib.parse import urlparse from django.utils.encoding import iri_to_uri +from django.utils.html import escape from django.utils.xmlutils import SimplerXMLGenerator @@ -95,11 +96,15 @@ class Stylesheet: return self._mimetype def __str__(self): - data = [f'href="{self.url}"'] - if self.mimetype is not None: - data.append(f'type="{self.mimetype}"') - if self.media is not None: - data.append(f'media="{self.media}"') + url = escape(iri_to_uri(self._url)) + mimetype = escape(self.mimetype) if self.mimetype is not None else None + media = escape(self.media) if self.media is not None else None + + data = [f'href="{url}"'] + if mimetype is not None: + data.append(f'type="{mimetype}"') + if media is not None: + data.append(f'media="{media}"') return " ".join(data) def __repr__(self): diff --git a/docs/releases/5.2.9.txt b/docs/releases/5.2.9.txt index 5698bdc206..bb762640b0 100644 --- a/docs/releases/5.2.9.txt +++ b/docs/releases/5.2.9.txt @@ -9,4 +9,6 @@ Django 5.2.9 fixes several bugs in 5.2.8. Bugfixes ======== -* ... +* Fixed a bug where ``django.utils.feedgenerator.Stylesheet.__str__()`` did not + escape the ``url``, ``mimetype``, and ``media`` attributes, potentially + leading to invalid XML markup (:ticket:`36733`). diff --git a/tests/utils_tests/test_feedgenerator.py b/tests/utils_tests/test_feedgenerator.py index 28a1afc96e..8ce9cc1dbf 100644 --- a/tests/utils_tests/test_feedgenerator.py +++ b/tests/utils_tests/test_feedgenerator.py @@ -159,3 +159,20 @@ class FeedgeneratorTests(SimpleTestCase): str(stylesheet), 'href="test.css" type="text/css" media="screen"' ) m.assert_called_once() + + def test_stylesheet_attribute_escaping(self): + """ + Stylesheet.__str__() should escape attribute values. + """ + style = feedgenerator.Stylesheet( + url='http://example.com/style.css?foo="bar"&baz=<>', + mimetype='text/css; charset="utf-8"', + media='screen and (max-width: "600px")', + ) + + self.assertEqual( + str(style), + 'href="http://example.com/style.css?foo=%22bar%22&baz=%3C%3E" ' + 'type="text/css; charset="utf-8"" ' + 'media="screen and (max-width: "600px")"', + )