diff --git a/pyproject.toml b/pyproject.toml index f433a48520..f42a858048 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,7 @@ dependencies = [ "drf_ujson2==1.7.*", "geoip2==4.*", "importlib_metadata==6.6.*", # Polyfill, we can probably drop this once we require Python 3.10+ + "idna", "isoweek", "jsonschema", "kombu==5.2.*", diff --git a/src/pretix/base/email.py b/src/pretix/base/email.py index 3462315153..378b105cf3 100644 --- a/src/pretix/base/email.py +++ b/src/pretix/base/email.py @@ -420,7 +420,7 @@ def base_placeholders(sender, **kwargs): 'order': 'F8VVL', 'secret': '6zzjnumtsx136ddy', 'hash': '98kusd8ofsj8dnkd' - } + }, ), ), SimpleFunctionalMailTextPlaceholder( diff --git a/src/pretix/base/templatetags/rich_text.py b/src/pretix/base/templatetags/rich_text.py index c4a8a53d7e..e8f608c9b0 100644 --- a/src/pretix/base/templatetags/rich_text.py +++ b/src/pretix/base/templatetags/rich_text.py @@ -36,6 +36,7 @@ import re import urllib.parse import bleach +import idna import markdown from bleach import DEFAULT_CALLBACKS from bleach.linkifier import build_email_re, build_url_re @@ -121,6 +122,12 @@ def safelink_callback(attrs, new=False): return attrs +def idna_decode_safe(src): + v = idna.decode(src) + + return v + + def truelink_callback(attrs, new=False): """ Tries to prevent "phishing" attacks in which a link looks like it points to a safe place but instead @@ -136,20 +143,40 @@ def truelink_callback(attrs, new=False): https://maps.google.com """ - text = re.sub(r'[^a-zA-Z0-9.\-/_ ]', '', attrs.get('_text')) # clean up link text + text = re.sub(r'[^a-zA-Z0-9:.\-/_ ]', '', attrs.get('_text')) # clean up link text url = attrs.get((None, 'href'), '/') href_url = urllib.parse.urlparse(url) + strip_http = False if (None, 'href') in attrs and URL_RE.match(text) and href_url.scheme not in ('tel', 'mailto'): + if URL_RE.match(attrs.get('_text').strip()): # maybe we cleaned up too much + text = attrs.get('_text').strip() # link text looks like a url if text.startswith('//'): text = 'https:' + text elif not text.startswith('http'): + strip_http = True text = 'https://' + text text_url = urllib.parse.urlparse(text) - if text_url.netloc != href_url.netloc or not href_url.path.startswith(href_url.path): + + if href_url.netloc.startswith('xn--'): + href_netloc_nice = idna_decode_safe(href_url.netloc) + else: + href_netloc_nice = href_url.netloc + + if text_url.netloc not in (href_url.netloc, href_netloc_nice) or not href_url.path.startswith(text_url.path): # link text contains an URL that has a different base than the actual URL attrs['_text'] = attrs[None, 'href'] + text_url = href_url + + if text_url.netloc.startswith('xn--'): + # Show punicode nicely + text_netloc_nice = idna_decode_safe(text_url.netloc) + url = text_url._replace(netloc=text_netloc_nice, scheme=href_url.scheme).geturl() + if strip_http: + url = url[len(href_url.scheme + '://'):] + attrs['_text'] = url + return attrs diff --git a/src/pretix/control/templates/pretixcontrol/event/index.html b/src/pretix/control/templates/pretixcontrol/event/index.html index 88f5fd3748..d28b29ae45 100644 --- a/src/pretix/control/templates/pretixcontrol/event/index.html +++ b/src/pretix/control/templates/pretixcontrol/event/index.html @@ -18,7 +18,7 @@
{% trans "Shop URL:" %} - {% abseventurl request.event "presale:event.index" %} + {% humanabseventurl request.event "presale:event.index" %}