diff --git a/src/pretix/base/email.py b/src/pretix/base/email.py index 3462315153..aa2ae2d21f 100644 --- a/src/pretix/base/email.py +++ b/src/pretix/base/email.py @@ -134,8 +134,11 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer): def template_name(self): raise NotImplementedError() + def compile_markdown(self, plaintext): + return markdown_compile_email(plaintext) + def render(self, plain_body: str, plain_signature: str, subject: str, order, position) -> str: - body_md = markdown_compile_email(plain_body) + body_md = self.compile_markdown(plain_body) htmlctx = { 'site': settings.PRETIX_INSTANCE_NAME, 'site_url': settings.SITE_URL, @@ -153,7 +156,7 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer): if plain_signature: signature_md = plain_signature.replace('\n', '
\n') - signature_md = markdown_compile_email(signature_md) + signature_md = self.compile_markdown(signature_md) htmlctx['signature'] = signature_md if order: diff --git a/src/pretix/base/templatetags/rich_text.py b/src/pretix/base/templatetags/rich_text.py index 8bba7951d4..ee2d5703ef 100644 --- a/src/pretix/base/templatetags/rich_text.py +++ b/src/pretix/base/templatetags/rich_text.py @@ -292,7 +292,7 @@ class LinkifyAndCleanExtension(Extension): ) -def markdown_compile_email(source): +def markdown_compile_email(source, allowed_tags=ALLOWED_TAGS, allowed_attributes=ALLOWED_ATTRIBUTES): linker = bleach.Linker( url_re=URL_RE, email_re=EMAIL_RE, @@ -306,8 +306,8 @@ def markdown_compile_email(source): EmailNl2BrExtension(), LinkifyAndCleanExtension( linker, - tags=ALLOWED_TAGS, - attributes=ALLOWED_ATTRIBUTES, + tags=allowed_tags, + attributes=allowed_attributes, protocols=ALLOWED_PROTOCOLS, strip=False, ) diff --git a/src/tests/base/test_rich_text.py b/src/tests/base/test_rich_text.py index 435b3d5408..6a952ad2e6 100644 --- a/src/tests/base/test_rich_text.py +++ b/src/tests/base/test_rich_text.py @@ -22,79 +22,122 @@ import pytest from pretix.base.templatetags.rich_text import ( - markdown_compile_email, rich_text, rich_text_snippet, + ALLOWED_ATTRIBUTES, ALLOWED_TAGS, markdown_compile_email, rich_text, + rich_text_snippet, ) -@pytest.mark.parametrize("link", [ - # Test link detection - ("google.com", - 'google.com'), - # Test link escaping - ("google\\.com", 'google.com'), - # Test abslink_callback - ("[Call](tel:+12345)", - 'Call'), - ("[Foo](/foo)", - 'Foo'), - ("mail@example.org", - 'mail@example.org'), - # Test truelink_callback - ('evilsite.com', - 'evilsite.com'), - ('cool-example.eu', - 'cool-example.eu'), - ('Evil GmbH & Co. KG', - 'Evil GmbH & Co. KG'), - ('Evil Site', - 'Evil Site'), - ('evilsite.com', - 'evilsite.com'), - ('goodsite.com', - 'https://evilsite.com'), - ('goodsite.com', - 'https://goodsite.com.evilsite.com'), - ('evilsite.com', - 'evilsite.com'), - ('broken', 'broken'), -]) +@pytest.mark.parametrize( + "link", + [ + # Test link detection + ( + "google.com", + 'google.com', + ), + # Test link escaping + ("google\\.com", "google.com"), + # Test abslink_callback + ("[Call](tel:+12345)", 'Call'), + ( + "[Foo](/foo)", + 'Foo', + ), + ("mail@example.org", 'mail@example.org'), + # Test truelink_callback + ( + "evilsite.com", + 'evilsite.com', + ), + ( + "cool-example.eu", + 'cool-example.eu', + ), + ( + 'Evil GmbH & Co. KG', + 'Evil GmbH & Co. KG', + ), + ( + 'Evil Site', + 'Evil Site', + ), + ( + 'evilsite.com', + 'evilsite.com', + ), + ( + 'goodsite.com', + 'https://evilsite.com', + ), + ( + 'goodsite.com', + 'https://goodsite.com.evilsite.com', + ), + ( + 'evilsite.com', + 'evilsite.com', + ), + ("broken", "broken"), + ], +) def test_linkify_abs(link): input, output = link assert rich_text_snippet(input, safelinks=False) == output - assert rich_text(input, safelinks=False) == f'

{output}

' - assert markdown_compile_email(input) == f'

{output}

' + assert rich_text(input, safelinks=False) == f"

{output}

" + assert markdown_compile_email(input) == f"

{output}

" -@pytest.mark.parametrize("content,result", [ - ('a\nb', '

a
\nb

'), - ('a \nb', '

a
\nb

'), - ('a\n\nb', '

a

\n

b

'), -]) +@pytest.mark.parametrize( + "content,result", + [ + ("a\nb", "

a
\nb

"), + ("a \nb", "

a
\nb

"), + ("a\n\nb", "

a

\n

b

"), + ], +) def test_newline_handling(content, result): assert rich_text(content, safelinks=False) == result -@pytest.mark.parametrize("content,result", [ - ('a\nb', '

a\nb

'), - ('a \nb', '

a
\nb

'), - ('a\n\nb', '

a

\n

b

'), -]) +@pytest.mark.parametrize( + "content,result", + [ + ("a\nb", "

a\nb

"), + ("a \nb", "

a
\nb

"), + ("a\n\nb", "

a

\n

b

"), + ], +) def test_newline_handling_email(content, result): assert markdown_compile_email(content) == result -@pytest.mark.parametrize("content,result,result_snippet", [ - # attributes - ('foo', '

foo

', 'foo'), - ('foo', - '

foo

', - 'foo'), - # protocols - ('foo', '

foo

', 'foo'), - # tags - ('', '<script>foo</script>', 'foo'), -]) +@pytest.mark.parametrize( + "content,result,result_snippet", + [ + # attributes + ('foo', "

foo

", "foo"), + ( + 'foo', + "

foo

", + "foo", + ), + # protocols + ('foo', "

foo

", "foo"), + # tags + ("", "<script>foo</script>", "foo"), + ], +) def test_cleanup(content, result, result_snippet): assert rich_text(content) == result assert rich_text_snippet(content) == result_snippet assert markdown_compile_email(content) == result + + +def test_markdown_email_custom_allowlist(): + source = "![my image](https://example.org/my-image.jpg)" + html = markdown_compile_email( + source, + allowed_tags=ALLOWED_TAGS + ["img"], + allowed_attributes=dict(ALLOWED_ATTRIBUTES, img=["src", "alt", "title"]), + ) + assert html == '

my image

'