Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dev/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ghcr.io/ietf-tools/datatracker-app-base:20250819T1508
FROM ghcr.io/ietf-tools/datatracker-app-base:20250821T1359
LABEL maintainer="IETF Tools Team <tools-discuss@ietf.org>"

ENV DEBIAN_FRONTEND=noninteractive
Expand Down
2 changes: 1 addition & 1 deletion dev/build/TARGET_BASE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
20250819T1508
20250821T1359

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions ietf/utils/templatetags/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from django.template import Context, Origin, Template
from django.test import override_settings

from ietf.utils.templatetags.textfilters import linkify
from ietf.utils.test_utils import TestCase
import debug # pyflakes: ignore

Expand Down Expand Up @@ -39,3 +40,68 @@ def test_origin_outside_base_dir(self):
output = template.render(Context())
self.assertNotIn(component, output,
'Full path components should not be revealed in html')


class TextfiltersTests(TestCase):
def test_linkify(self):
# Cases with autoescape = True (the default)
self.assertEqual(
linkify("plain string"),
"plain string",
)
self.assertEqual(
linkify("https://www.ietf.org"),
'<a href="https://www.ietf.org">https://www.ietf.org</a>',
)
self.assertEqual(
linkify('<a href="https://www.ietf.org">IETF</a>'),
(
'&lt;a href=&quot;<a href="https://www.ietf.org">https://www.ietf.org</a>&quot;&gt;IETF&lt;/a&gt;'
),
)
self.assertEqual(
linkify("somebody@example.com"),
'<a href="mailto:somebody@example.com">somebody@example.com</a>',
)
self.assertEqual(
linkify("Some Body <somebody@example.com>"),
(
'Some Body &lt;<a href="mailto:somebody@example.com">'
'somebody@example.com</a>&gt;'
),
)
self.assertEqual(
linkify("<script>alert('h4x0r3d');</script>"),
"&lt;script&gt;alert(&#x27;h4x0r3d&#x27;);&lt;/script&gt;",
)

# Cases with autoescape = False (these are dangerous and assume the caller
# has sanitized already)
self.assertEqual(
linkify("plain string", autoescape=False),
"plain string",
)
self.assertEqual(
linkify("https://www.ietf.org", autoescape=False),
'<a href="https://www.ietf.org">https://www.ietf.org</a>',
)
self.assertEqual(
linkify('<a href="https://www.ietf.org">IETF</a>', autoescape=False),
'<a href="https://www.ietf.org">IETF</a>',
)
self.assertEqual(
linkify("somebody@example.com", autoescape=False),
'<a href="mailto:somebody@example.com">somebody@example.com</a>',
)
# bleach.Linkifier translates the < -> &lt; and > -> &gt; on this one
self.assertEqual(
linkify("Some Body <somebody@example.com>", autoescape=False),
(
'Some Body &lt;<a href="mailto:somebody@example.com">'
'somebody@example.com</a>&gt;'
),
)
self.assertEqual(
linkify("<script>alert('friendly script');</script>", autoescape=False),
"<script>alert('friendly script');</script>",
)
10 changes: 7 additions & 3 deletions ietf/utils/templatetags/textfilters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from django import template
from django.conf import settings
from django.template.defaultfilters import stringfilter
from django.utils.html import conditional_escape
from django.utils.safestring import mark_safe

import debug # pyflakes:ignore
Expand Down Expand Up @@ -71,10 +72,13 @@ def texescape_filter(value):
"A TeX escape filter"
return texescape(value)

@register.filter
@register.filter(needs_autoescape=True)
@stringfilter
def linkify(value):
text = mark_safe(_linkify(value))
def linkify(value, autoescape=True):
if autoescape:
# Escape unless the input was already a SafeString
value = conditional_escape(value)
text = mark_safe(_linkify(value)) # _linkify is a safe operation
return text

@register.filter
Expand Down
6 changes: 6 additions & 0 deletions ietf/utils/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ def check_url_validity(attrs, new=False):


def linkify(text):
"""Convert URL-ish substrings into HTML links

This does no sanitization whatsoever. Caller must sanitize the input or output as
contextually appropriate. Do not call `mark_safe()` on the output if the input is
user-provided unless it has been sanitized or escaped.
"""
return _bleach_linker.linkify(text)


Expand Down
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ python-json-logger>=3.1.0
python-magic==0.4.18 # Versions beyond the yanked .19 and .20 introduce form failures
pymemcache>=4.0.0 # for django.core.cache.backends.memcached.PyMemcacheCache
python-mimeparse>=1.6 # from TastyPie
pytz==2022.2.1 # Pinned as changes need to be vetted for their effect on Meeting fields
types-pytz==2022.2.1 # match pytz version
pytz==2025.2 # Pinned as changes need to be vetted for their effect on Meeting fields
types-pytz==2025.2.0.20250809 # match pytz versionrequests>=2.31.0
requests>=2.31.0
types-requests>=2.27.1
requests-mock>=1.9.3
Expand Down
Loading