diff --git a/src/pretix/base/email.py b/src/pretix/base/email.py index 395513286a..ab71fb45f1 100644 --- a/src/pretix/base/email.py +++ b/src/pretix/base/email.py @@ -24,6 +24,7 @@ from itertools import groupby from smtplib import SMTPResponseException from typing import TypeVar +import bleach import css_inline from django.conf import settings from django.core.mail.backends.smtp import EmailBackend @@ -34,7 +35,10 @@ from django.utils.translation import get_language, gettext_lazy as _ from pretix.base.models import Event from pretix.base.signals import register_html_mail_renderers -from pretix.base.templatetags.rich_text import markdown_compile_email +from pretix.base.templatetags.rich_text import ( + DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback, + markdown_compile_email, truelink_callback, +) from pretix.helpers.format import SafeFormatter, format_map from pretix.base.services.placeholders import ( # noqa @@ -139,7 +143,18 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer): def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str: body_md = self.compile_markdown(plain_body, context) if context: - body_md = format_map(body_md, context=context, mode=SafeFormatter.MODE_RICH_TO_HTML) + linker = bleach.Linker( + url_re=URL_RE, + email_re=EMAIL_RE, + callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback], + parse_email=True + ) + body_md = format_map( + body_md, + context=context, + mode=SafeFormatter.MODE_RICH_TO_HTML, + linkifier=linker + ) htmlctx = { 'site': settings.PRETIX_INSTANCE_NAME, 'site_url': settings.SITE_URL, diff --git a/src/pretix/helpers/format.py b/src/pretix/helpers/format.py index ebbdba8eb9..a79b869e5e 100644 --- a/src/pretix/helpers/format.py +++ b/src/pretix/helpers/format.py @@ -45,10 +45,11 @@ class SafeFormatter(Formatter): MODE_RICH_TO_PLAIN = 1 MODE_RICH_TO_HTML = 2 - def __init__(self, context, raise_on_missing=False, mode=MODE_RICH_TO_PLAIN): + def __init__(self, context, raise_on_missing=False, mode=MODE_RICH_TO_PLAIN, linkifier=None): self.context = context self.raise_on_missing = raise_on_missing self.mode = mode + self.linkifier = linkifier def get_field(self, field_name, args, kwargs): return self.get_value(field_name, args, kwargs), field_name @@ -68,6 +69,8 @@ class SafeFormatter(Formatter): value = str(value) if self.mode == self.MODE_RICH_TO_HTML: value = conditional_escape(value) + if self.linkifier: + value = self.linkifier.linkify(value) return value def format_field(self, value, format_spec): @@ -75,7 +78,7 @@ class SafeFormatter(Formatter): return super().format_field(self._prepare_value(value), '') -def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN): +def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None): if not isinstance(template, str): template = str(template) - return SafeFormatter(context, raise_on_missing, mode=mode).format(template) + return SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template) diff --git a/src/tests/base/test_mail.py b/src/tests/base/test_mail.py index da276aa970..1e15460a15 100644 --- a/src/tests/base/test_mail.py +++ b/src/tests/base/test_mail.py @@ -225,16 +225,20 @@ def test_placeholder_html_rendering_from_string(env): template = LazyI18nString({ "en": "Event name: {event}\n\nPayment info:\n{payment_info}\n\n**Meta**: {meta_Test}\n\n" "Event website: [{event}](https://example.org/{event_slug})\n\n" - "Other website: [{event}]({meta_Website})" + "Other website: [{event}]({meta_Website})\n\n" + "URL: {url}\n\n" + "URL with text: Test" }) djmail.outbox = [] event, user, organizer = env event.name = "event & co. kg" event.save() - mail('dummy@dummy.dummy', '{event} Test subject', template, get_email_context( + ctx = get_email_context( event=event, payment_info="**IBAN**: 123 \n**BIC**: 456", - ), event) + ) + ctx["url"] = "https://google.com" + mail('dummy@dummy.dummy', '{event} Test subject', template, ctx, event) assert len(djmail.outbox) == 1 assert djmail.outbox[0].to == [user.email] @@ -243,6 +247,8 @@ def test_placeholder_html_rendering_from_string(env): assert 'Other website: [event & co. kg](https://example.com)' in djmail.outbox[0].body assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body assert '**Meta**: *Beep*' in djmail.outbox[0].body + assert 'URL: https://google.com' in djmail.outbox[0].body + assert 'URL with text: Test' in djmail.outbox[0].body assert '<' not in djmail.outbox[0].body assert '&' not in djmail.outbox[0].body html = _extract_html(djmail.outbox[0]) @@ -258,3 +264,11 @@ def test_placeholder_html_rendering_from_string(env): r'Other website: <strong>event & co. kg</strong>', html ) + assert re.search( + r'URL: https://google.com', + html + ) + assert re.search( + r'URL with text: Test', + html + )