Skip to content

Commit b7da3d7

Browse files
fix: escape linkify filter input (#9389)
* fix: escape linkify filter input * test: exercise linkify * chore: lint
1 parent 1e451fb commit b7da3d7

File tree

3 files changed

+79
-3
lines changed

3 files changed

+79
-3
lines changed

ietf/utils/templatetags/tests.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.template import Context, Origin, Template
44
from django.test import override_settings
55

6+
from ietf.utils.templatetags.textfilters import linkify
67
from ietf.utils.test_utils import TestCase
78
import debug # pyflakes: ignore
89

@@ -39,3 +40,68 @@ def test_origin_outside_base_dir(self):
3940
output = template.render(Context())
4041
self.assertNotIn(component, output,
4142
'Full path components should not be revealed in html')
43+
44+
45+
class TextfiltersTests(TestCase):
46+
def test_linkify(self):
47+
# Cases with autoescape = True (the default)
48+
self.assertEqual(
49+
linkify("plain string"),
50+
"plain string",
51+
)
52+
self.assertEqual(
53+
linkify("https://www.ietf.org"),
54+
'<a href="https://www.ietf.org">https://www.ietf.org</a>',
55+
)
56+
self.assertEqual(
57+
linkify('<a href="https://www.ietf.org">IETF</a>'),
58+
(
59+
'&lt;a href=&quot;<a href="https://www.ietf.org">https://www.ietf.org</a>&quot;&gt;IETF&lt;/a&gt;'
60+
),
61+
)
62+
self.assertEqual(
63+
linkify("somebody@example.com"),
64+
'<a href="mailto:somebody@example.com">somebody@example.com</a>',
65+
)
66+
self.assertEqual(
67+
linkify("Some Body <somebody@example.com>"),
68+
(
69+
'Some Body &lt;<a href="mailto:somebody@example.com">'
70+
'somebody@example.com</a>&gt;'
71+
),
72+
)
73+
self.assertEqual(
74+
linkify("<script>alert('h4x0r3d');</script>"),
75+
"&lt;script&gt;alert(&#x27;h4x0r3d&#x27;);&lt;/script&gt;",
76+
)
77+
78+
# Cases with autoescape = False (these are dangerous and assume the caller
79+
# has sanitized already)
80+
self.assertEqual(
81+
linkify("plain string", autoescape=False),
82+
"plain string",
83+
)
84+
self.assertEqual(
85+
linkify("https://www.ietf.org", autoescape=False),
86+
'<a href="https://www.ietf.org">https://www.ietf.org</a>',
87+
)
88+
self.assertEqual(
89+
linkify('<a href="https://www.ietf.org">IETF</a>', autoescape=False),
90+
'<a href="https://www.ietf.org">IETF</a>',
91+
)
92+
self.assertEqual(
93+
linkify("somebody@example.com", autoescape=False),
94+
'<a href="mailto:somebody@example.com">somebody@example.com</a>',
95+
)
96+
# bleach.Linkifier translates the < -> &lt; and > -> &gt; on this one
97+
self.assertEqual(
98+
linkify("Some Body <somebody@example.com>", autoescape=False),
99+
(
100+
'Some Body &lt;<a href="mailto:somebody@example.com">'
101+
'somebody@example.com</a>&gt;'
102+
),
103+
)
104+
self.assertEqual(
105+
linkify("<script>alert('friendly script');</script>", autoescape=False),
106+
"<script>alert('friendly script');</script>",
107+
)

ietf/utils/templatetags/textfilters.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from django import template
88
from django.conf import settings
99
from django.template.defaultfilters import stringfilter
10+
from django.utils.html import conditional_escape
1011
from django.utils.safestring import mark_safe
1112

1213
import debug # pyflakes:ignore
@@ -71,10 +72,13 @@ def texescape_filter(value):
7172
"A TeX escape filter"
7273
return texescape(value)
7374

74-
@register.filter
75+
@register.filter(needs_autoescape=True)
7576
@stringfilter
76-
def linkify(value):
77-
text = mark_safe(_linkify(value))
77+
def linkify(value, autoescape=True):
78+
if autoescape:
79+
# Escape unless the input was already a SafeString
80+
value = conditional_escape(value)
81+
text = mark_safe(_linkify(value)) # _linkify is a safe operation
7882
return text
7983

8084
@register.filter

ietf/utils/text.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ def check_url_validity(attrs, new=False):
6060

6161

6262
def linkify(text):
63+
"""Convert URL-ish substrings into HTML links
64+
65+
This does no sanitization whatsoever. Caller must sanitize the input or output as
66+
contextually appropriate. Do not call `mark_safe()` on the output if the input is
67+
user-provided unless it has been sanitized or escaped.
68+
"""
6369
return _bleach_linker.linkify(text)
6470

6571

0 commit comments

Comments
 (0)