diff --git a/src/core/markdown.py b/src/core/markdown.py index 06e3365b4e0f1f3949aed2a5aad7faad5eaba16d..b6011d3c24e7a115f7535676373a083a41fc9211 100644 --- a/src/core/markdown.py +++ b/src/core/markdown.py @@ -134,7 +134,7 @@ class MyHtmlRenderer(HTMLRenderer): from .utils import resolve_internal_url # attempt resolving an internal URL - if resolved_internal_url := resolve_internal_url(url, fallback_as_is=False): + if resolved_internal_url := resolve_internal_url(url, accept_http_https=False, fallback_as_is=False): url = resolved_internal_url # derive external link (i.e. apply dereferer), if its not an internal or trusted location diff --git a/src/core/models/rooms.py b/src/core/models/rooms.py index 21e0c1ee2991d1e131403f2a6a1f902e6f1580d7..9a4c275f284094bd436764ec7fc3465b84d7ae78 100644 --- a/src/core/models/rooms.py +++ b/src/core/models/rooms.py @@ -8,7 +8,6 @@ from django.conf import settings from django.contrib.contenttypes.fields import GenericRelation from django.contrib.postgres.fields import DateTimeRangeField from django.core.exceptions import ValidationError -from django.core.validators import URLValidator from django.db import models from django.db.models import Q, QuerySet from django.utils.text import slugify @@ -21,7 +20,7 @@ from core.models.base_managers import ConferenceManagerMixin from core.models.conference import Conference, ConferenceMember from core.models.shared import BackendMixin from core.models.tags import TagItem -from core.utils import str2bool +from core.utils import resolve_internal_url, str2bool from core.validators import FileSizeValidator, ImageDimensionValidator @@ -483,10 +482,7 @@ class RoomLink(models.Model): def clean(self): if self.link_type in RoomLink.URL_LINKTYPES: - validator = URLValidator() - try: - validator(self.link) - except ValidationError: + if not resolve_internal_url(self.link, fallback_as_is=False): raise ValidationError({'link': _('RoomLink__link__must_be_url')}) def __str__(self): diff --git a/src/core/tests/utils.py b/src/core/tests/utils.py index b4d4ca33b18fbbabcd22a4537bf89e09e4cadc59..489713310d72750ffc6b17b852ba942608059780 100644 --- a/src/core/tests/utils.py +++ b/src/core/tests/utils.py @@ -90,6 +90,18 @@ class InternalUrlTests(TestCase): for check in checks: self.assertEqual(check[1], resolve_internal_url(check[0])) + # combinations of accept_http_https and fallback_as_is with a https URL + self.assertEqual('https://ccc.de/', resolve_internal_url('https://ccc.de/', accept_http_https=True, fallback_as_is=True)) + self.assertEqual('https://ccc.de/', resolve_internal_url('https://ccc.de/', accept_http_https=True, fallback_as_is=False)) + self.assertEqual('https://ccc.de/', resolve_internal_url('https://ccc.de/', accept_http_https=False, fallback_as_is=True)) + self.assertIsNone(resolve_internal_url('https://ccc.de/', accept_http_https=False, fallback_as_is=False)) + + # combinations of accept_http_https and fallback_as_is with an invalid URL + self.assertEqual('foo', resolve_internal_url('foo', accept_http_https=True, fallback_as_is=True)) + self.assertIsNone(resolve_internal_url('foo', accept_http_https=True, fallback_as_is=False)) + self.assertEqual('foo', resolve_internal_url('foo', accept_http_https=False, fallback_as_is=True)) + self.assertIsNone(resolve_internal_url('foo', accept_http_https=False, fallback_as_is=False)) + @override_settings(ADDITIONAL_LINK_PROTOCOLS={'c3nav': 'https://test.c3nav.de/l/VALUE', 'ccc': 'https://ccc.de/?goto=VALUE'}) def test_additional_link_protocols(self): checks = [ diff --git a/src/core/utils.py b/src/core/utils.py index c445d70d362c295643fd6193a3aa9bc3a9df6c13..cd3c151c26c58d4ab2c110e53310319ad0d8a4ea 100644 --- a/src/core/utils.py +++ b/src/core/utils.py @@ -156,7 +156,7 @@ def mask_url(url): return urlunparse(masked) -def resolve_internal_url(url: str, fallback_as_is: bool = True) -> str | None: +def resolve_internal_url(url: str, accept_http_https: bool = True, fallback_as_is: bool = True) -> str | None: """ Resolves special URLs like - conference://url_resolver_name @@ -164,7 +164,8 @@ def resolve_internal_url(url: str, fallback_as_is: bool = True) -> str | None: - event://slug - wiki://slug - Regular URLs (i.e. https://...) are returned as-is by default but may be resolved as None (i.e. no match). + Regular URLs (i.e. https://...) are accepted by default, too. + Everything else (unparseable, unknown protocl) are returned as-is by default but may be resolved as None (i.e. no match). """ try: protocol, rhs = url.split('://', maxsplit=1) @@ -175,6 +176,9 @@ def resolve_internal_url(url: str, fallback_as_is: bool = True) -> str | None: remainder = rhs_splitted[0] query_string = '' if len(rhs_splitted) == 1 else rhs_splitted[1] + if accept_http_https and protocol in ['http', 'https']: + return url + try: from core.templatetags.hub_absolute import hub_absolute # pylint: disable=import-outside-toplevel