From cbc8618805f015d65f82e95902ee95de3ee81acb Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 5 Jan 2026 16:45:33 +0100 Subject: [PATCH] Turn scheme-less URLs into HTTPS instead of HTTP links (#1225) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Turn scheme-less URLs into HTTPS instead of HTTP links Signed-off-by: Max Kunzelmann * fix bug, add tests * use single linker instance * simplify logic * lint --------- Signed-off-by: Max Kunzelmann Co-authored-by: Sascha Ißbrücker --- bookmarks/templatetags/shared.py | 19 +++++++++- .../tests/test_bookmarks_list_template.py | 36 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/bookmarks/templatetags/shared.py b/bookmarks/templatetags/shared.py index 1f1a7f6..40676ab 100644 --- a/bookmarks/templatetags/shared.py +++ b/bookmarks/templatetags/shared.py @@ -2,6 +2,7 @@ import re import bleach import markdown +from bleach.linkifier import DEFAULT_CALLBACKS, Linker from bleach_allowlist import markdown_attrs, markdown_tags from django import template from django.utils.safestring import mark_safe @@ -78,6 +79,22 @@ class HtmlMinNode(template.Node): return output +def schemeless_urls_to_https(attrs, _new): + href_key = (None, "href") + if href_key not in attrs: + return attrs + + if attrs.get("_text", "").startswith("http://"): + # The original text explicitly specifies http://, so keep it + return attrs + + attrs[href_key] = re.sub(r"^http://", "https://", attrs[href_key]) + return attrs + + +linker = Linker(callbacks=[*DEFAULT_CALLBACKS, schemeless_urls_to_https]) + + @register.simple_tag(name="markdown", takes_context=True) def render_markdown(context, markdown_text): # naive approach to reusing the renderer for a single request @@ -90,7 +107,7 @@ def render_markdown(context, markdown_text): as_html = renderer.convert(markdown_text) sanitized_html = bleach.clean(as_html, markdown_tags, markdown_attrs) - linkified_html = bleach.linkify(sanitized_html) + linkified_html = linker.linkify(sanitized_html) return mark_safe(linkified_html) diff --git a/bookmarks/tests/test_bookmarks_list_template.py b/bookmarks/tests/test_bookmarks_list_template.py index 04d2aa3..48ec034 100644 --- a/bookmarks/tests/test_bookmarks_list_template.py +++ b/bookmarks/tests/test_bookmarks_list_template.py @@ -919,6 +919,42 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin): note_html = '

https://example.com

' self.assertNotes(html, note_html, 1) + def test_note_linkify_converts_schemeless_urls_to_https(self): + # Scheme-less URL should become HTTPS + self.setup_bookmark(notes="Example: example.com") + html = self.render_template() + + note_html = '

Example: example.com

' + self.assertNotes(html, note_html, 1) + + # Explicit http:// should stay as http:// + self.setup_bookmark(notes="Example: http://example.com") + html = self.render_template() + + note_html = '

Example: http://example.com

' + self.assertNotes(html, note_html, 1) + + # Explicit https:// should stay as https:// + self.setup_bookmark(notes="Example: https://example.com") + html = self.render_template() + + note_html = '

Example: https://example.com

' + self.assertNotes(html, note_html, 1) + + # Email addresses should not be affected + self.setup_bookmark(notes="Contact: hello@example.com") + html = self.render_template() + + note_html = "

Contact: hello@example.com

" + self.assertNotes(html, note_html, 1) + + # ftp:// should not be converted to https + self.setup_bookmark(notes="FTP: ftp://ftp.example.com") + html = self.render_template() + + note_html = '

FTP: ftp://ftp.example.com

' + self.assertNotes(html, note_html, 1) + def test_note_cleans_html(self): self.setup_bookmark(notes='') self.setup_bookmark(