Skip to content

Commit e05f2a7

Browse files
varunkasyapjacobtylerwalls
authored andcommitted
Fixed #36733 -- Escaped attributes in Stylesheet.__str__().
Thanks Mustafa Barakat for the report, Baptiste Mispelon for the triage, and Jake Howard for the review.
1 parent b07298a commit e05f2a7

4 files changed

Lines changed: 40 additions & 22 deletions

File tree

django/utils/feedgenerator.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from io import StringIO
2929
from urllib.parse import urlparse
3030

31+
from django.forms.utils import flatatt
3132
from django.utils.encoding import iri_to_uri
3233
from django.utils.xmlutils import SimplerXMLGenerator
3334

@@ -95,12 +96,12 @@ def mimetype(self):
9596
return self._mimetype
9697

9798
def __str__(self):
98-
data = [f'href="{self.url}"']
99-
if self.mimetype is not None:
100-
data.append(f'type="{self.mimetype}"')
101-
if self.media is not None:
102-
data.append(f'media="{self.media}"')
103-
return " ".join(data)
99+
attrs = {
100+
"href": iri_to_uri(self._url),
101+
"type": self.mimetype,
102+
"media": self.media,
103+
}
104+
return flatatt(attrs).strip()
104105

105106
def __repr__(self):
106107
return repr((self.url, self.mimetype, self.media))

docs/releases/5.2.9.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ Django 5.2.9 fixes several bugs in 5.2.8.
99
Bugfixes
1010
========
1111

12-
* ...
12+
* Fixed a bug in Django 5.2 where
13+
``django.utils.feedgenerator.Stylesheet.__str__()`` did not escape
14+
the ``url``, ``mimetype``, and ``media`` attributes, potentially leading
15+
to invalid XML markup (:ticket:`36733`).

tests/syndication_tests/tests.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -578,51 +578,51 @@ def test_stylesheets_none(self):
578578
def test_stylesheets(self):
579579
testdata = [
580580
# Plain strings.
581-
("/test.xsl", 'href="/test.xsl" type="text/xsl" media="screen"'),
582-
("/test.xslt", 'href="/test.xslt" type="text/xsl" media="screen"'),
583-
("/test.css", 'href="/test.css" type="text/css" media="screen"'),
581+
("/test.xsl", 'href="/test.xsl" media="screen" type="text/xsl"'),
582+
("/test.xslt", 'href="/test.xslt" media="screen" type="text/xsl"'),
583+
("/test.css", 'href="/test.css" media="screen" type="text/css"'),
584584
("/test", 'href="/test" media="screen"'),
585585
(
586586
"https://example.com/test.xsl",
587-
'href="https://example.com/test.xsl" type="text/xsl" media="screen"',
587+
'href="https://example.com/test.xsl" media="screen" type="text/xsl"',
588588
),
589589
(
590590
"https://example.com/test.css",
591-
'href="https://example.com/test.css" type="text/css" media="screen"',
591+
'href="https://example.com/test.css" media="screen" type="text/css"',
592592
),
593593
(
594594
"https://example.com/test",
595595
'href="https://example.com/test" media="screen"',
596596
),
597-
("/♥.xsl", 'href="/%E2%99%A5.xsl" type="text/xsl" media="screen"'),
597+
("/♥.xsl", 'href="/%E2%99%A5.xsl" media="screen" type="text/xsl"'),
598598
(
599599
static("stylesheet.xsl"),
600-
'href="/static/stylesheet.xsl" type="text/xsl" media="screen"',
600+
'href="/static/stylesheet.xsl" media="screen" type="text/xsl"',
601601
),
602602
(
603603
static("stylesheet.css"),
604-
'href="/static/stylesheet.css" type="text/css" media="screen"',
604+
'href="/static/stylesheet.css" media="screen" type="text/css"',
605605
),
606606
(static("stylesheet"), 'href="/static/stylesheet" media="screen"'),
607607
(
608608
reverse("syndication-xsl-stylesheet"),
609-
'href="/syndication/stylesheet.xsl" type="text/xsl" media="screen"',
609+
'href="/syndication/stylesheet.xsl" media="screen" type="text/xsl"',
610610
),
611611
(
612612
reverse_lazy("syndication-xsl-stylesheet"),
613-
'href="/syndication/stylesheet.xsl" type="text/xsl" media="screen"',
613+
'href="/syndication/stylesheet.xsl" media="screen" type="text/xsl"',
614614
),
615615
# Stylesheet objects.
616616
(
617617
Stylesheet("/test.xsl"),
618-
'href="/test.xsl" type="text/xsl" media="screen"',
618+
'href="/test.xsl" media="screen" type="text/xsl"',
619619
),
620620
(Stylesheet("/test.xsl", mimetype=None), 'href="/test.xsl" media="screen"'),
621621
(Stylesheet("/test.xsl", media=None), 'href="/test.xsl" type="text/xsl"'),
622622
(Stylesheet("/test.xsl", mimetype=None, media=None), 'href="/test.xsl"'),
623623
(
624624
Stylesheet("/test.xsl", mimetype="text/xml"),
625-
'href="/test.xsl" type="text/xml" media="screen"',
625+
'href="/test.xsl" media="screen" type="text/xml"',
626626
),
627627
]
628628
for stylesheet, expected in testdata:
@@ -642,12 +642,12 @@ def test_stylesheets_instructions_are_at_the_top(self):
642642
self.assertEqual(doc.childNodes[0].nodeName, "xml-stylesheet")
643643
self.assertEqual(
644644
doc.childNodes[0].data,
645-
'href="/stylesheet1.xsl" type="text/xsl" media="screen"',
645+
'href="/stylesheet1.xsl" media="screen" type="text/xsl"',
646646
)
647647
self.assertEqual(doc.childNodes[1].nodeName, "xml-stylesheet")
648648
self.assertEqual(
649649
doc.childNodes[1].data,
650-
'href="/stylesheet2.xsl" type="text/xsl" media="screen"',
650+
'href="/stylesheet2.xsl" media="screen" type="text/xsl"',
651651
)
652652

653653
def test_stylesheets_typeerror_if_str_or_stylesheet(self):

tests/utils_tests/test_feedgenerator.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,20 @@ def test_stylesheet_keeps_lazy_urls(self):
156156
stylesheet = feedgenerator.Stylesheet(SimpleLazyObject(m))
157157
m.assert_not_called()
158158
self.assertEqual(
159-
str(stylesheet), 'href="test.css" type="text/css" media="screen"'
159+
str(stylesheet), 'href="test.css" media="screen" type="text/css"'
160160
)
161161
m.assert_called_once()
162+
163+
def test_stylesheet_attribute_escaping(self):
164+
style = feedgenerator.Stylesheet(
165+
url='http://example.com/style.css?foo="bar"&baz=<>',
166+
mimetype='text/css; charset="utf-8"',
167+
media='screen and (max-width: "600px")',
168+
)
169+
170+
self.assertEqual(
171+
str(style),
172+
'href="http://example.com/style.css?foo=%22bar%22&amp;baz=%3C%3E" '
173+
'media="screen and (max-width: &quot;600px&quot;)" '
174+
'type="text/css; charset=&quot;utf-8&quot;"',
175+
)

0 commit comments

Comments
 (0)