forked from CGM_Public/pretix_original
Email: always use quoted-printable (#6617)
This commit is contained in:
@@ -54,7 +54,7 @@ from celery.exceptions import MaxRetriesExceededError
|
||||
from django.conf import settings
|
||||
from django.core.files.storage import default_storage
|
||||
from django.core.mail import EmailMultiAlternatives, SafeMIMEMultipart
|
||||
from django.core.mail.message import SafeMIMEText
|
||||
from django.core.mail.message import SafeMIMEText, utf8_charset_qp
|
||||
from django.db import connection, transaction
|
||||
from django.db.models import Q
|
||||
from django.dispatch import receiver
|
||||
@@ -380,6 +380,8 @@ def mail(email: Union[str, Sequence[str]], subject: Union[str, FormattedString],
|
||||
|
||||
|
||||
class CustomEmail(EmailMultiAlternatives):
|
||||
encoding = utf8_charset_qp
|
||||
|
||||
def _create_mime_attachment(self, content, mimetype):
|
||||
"""
|
||||
Convert the content, mimetype pair into a MIME attachment object.
|
||||
@@ -449,9 +451,9 @@ def mail_send_task(self, **kwargs) -> bool:
|
||||
|
||||
# Rewrite all <img> tags from real URLs or data URLs to inline attachments referred to by content ID
|
||||
if outgoing_mail.body_html is not None:
|
||||
html_message = SafeMIMEMultipart(_subtype='related', encoding=settings.DEFAULT_CHARSET)
|
||||
html_message = SafeMIMEMultipart(_subtype='related')
|
||||
html_with_cid, cid_images = replace_images_with_cid_paths(outgoing_mail.body_html)
|
||||
html_message.attach(SafeMIMEText(html_with_cid, 'html', settings.DEFAULT_CHARSET))
|
||||
html_message.attach(SafeMIMEText(html_with_cid, 'html', utf8_charset_qp))
|
||||
attach_cid_images(html_message, cid_images, verify_ssl=True)
|
||||
email.attach_alternative(html_message, "multipart/related")
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import quopri
|
||||
import re
|
||||
import socket
|
||||
from contextlib import contextmanager
|
||||
@@ -316,8 +317,10 @@ def _extract_html(mail):
|
||||
if "multipart/related" in mimetype:
|
||||
for sp in content._payload:
|
||||
if isinstance(sp, MIMEText):
|
||||
return sp._payload
|
||||
break
|
||||
if sp.get('content-transfer-encoding').startswith('quoted-printable'):
|
||||
return quopri.decodestring(sp.get_payload()).decode()
|
||||
else:
|
||||
return sp.get_payload()
|
||||
elif "text/html" in mimetype:
|
||||
return content
|
||||
|
||||
@@ -711,3 +714,29 @@ def test_send_mail_private_ip(res, use_ssl, allow_private_networks, env):
|
||||
assert m.status == OutgoingMail.STATUS_SENT
|
||||
else:
|
||||
assert m.status == OutgoingMail.STATUS_FAILED
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_mail_encoding(env, mailoutbox):
|
||||
m = OutgoingMail.objects.create(
|
||||
to=['recipient@example.com'],
|
||||
subject='Test',
|
||||
body_plain='Test öäü testtesttest',
|
||||
body_html='Test <b>ÖÄÜ</b>',
|
||||
sender='sender@example.com',
|
||||
)
|
||||
assert m.status == OutgoingMail.STATUS_QUEUED
|
||||
mail_send_task.apply(kwargs={
|
||||
'outgoing_mail': m.pk,
|
||||
}, max_retries=0)
|
||||
m.refresh_from_db()
|
||||
assert m.status == OutgoingMail.STATUS_SENT
|
||||
|
||||
assert len(mailoutbox) == 1
|
||||
mail_plain = mailoutbox[0].message().as_string()
|
||||
|
||||
assert "ä" not in mail_plain
|
||||
assert "Ä" not in mail_plain
|
||||
assert "Test =C3=B6=C3=A4=C3=BC" in mail_plain
|
||||
assert "Test <b>=C3=96=C3=84=C3=9C</b>" in mail_plain
|
||||
assert max(len(x) for x in mail_plain.splitlines()) <= 76
|
||||
|
||||
Reference in New Issue
Block a user