2015-07-22 23:47:32 +08:00
|
|
|
|
import mimetypes
|
2010-10-11 23:11:55 +08:00
|
|
|
|
import os
|
|
|
|
|
import shutil
|
2021-10-15 15:58:35 +08:00
|
|
|
|
import socket
|
2010-10-11 23:11:55 +08:00
|
|
|
|
import sys
|
2011-01-15 13:55:24 +08:00
|
|
|
|
import tempfile
|
2018-10-23 03:21:33 +08:00
|
|
|
|
from email import charset, message_from_binary_file, message_from_bytes
|
2016-04-02 17:41:47 +08:00
|
|
|
|
from email.header import Header
|
2015-01-28 20:35:27 +08:00
|
|
|
|
from email.mime.text import MIMEText
|
2016-12-01 18:38:01 +08:00
|
|
|
|
from email.utils import parseaddr
|
2017-01-07 19:11:46 +08:00
|
|
|
|
from io import StringIO
|
2019-11-06 16:33:07 +08:00
|
|
|
|
from pathlib import Path
|
2021-10-14 17:36:51 +08:00
|
|
|
|
from smtplib import SMTP, SMTPException
|
2013-07-12 02:58:06 +08:00
|
|
|
|
from ssl import SSLError
|
2021-10-15 15:58:35 +08:00
|
|
|
|
from unittest import mock, skipUnless
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
2010-10-11 23:11:55 +08:00
|
|
|
|
from django.core import mail
|
2015-01-28 20:35:27 +08:00
|
|
|
|
from django.core.mail import (
|
2019-07-02 20:15:32 +08:00
|
|
|
|
DNS_NAME,
|
|
|
|
|
EmailMessage,
|
|
|
|
|
EmailMultiAlternatives,
|
|
|
|
|
mail_admins,
|
|
|
|
|
mail_managers,
|
2015-01-28 20:35:27 +08:00
|
|
|
|
send_mail,
|
|
|
|
|
send_mass_mail,
|
|
|
|
|
)
|
|
|
|
|
from django.core.mail.backends import console, dummy, filebased, locmem, smtp
|
2016-04-02 17:41:47 +08:00
|
|
|
|
from django.core.mail.message import BadHeaderError, sanitize_address
|
2016-06-06 11:47:17 +08:00
|
|
|
|
from django.test import SimpleTestCase, override_settings
|
2016-06-03 07:41:13 +08:00
|
|
|
|
from django.test.utils import requires_tz_support
|
2017-01-27 03:58:33 +08:00
|
|
|
|
from django.utils.translation import gettext_lazy
|
2022-02-20 03:36:01 +08:00
|
|
|
|
from django.utils.version import PY311
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
try:
|
|
|
|
|
from aiosmtpd.controller import Controller
|
2022-02-04 03:24:19 +08:00
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
HAS_AIOSMTPD = True
|
|
|
|
|
except ImportError:
|
|
|
|
|
HAS_AIOSMTPD = False
|
|
|
|
|
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
2017-01-19 15:39:46 +08:00
|
|
|
|
class HeadersCheckMixin:
|
2013-08-20 07:04:50 +08:00
|
|
|
|
def assertMessageHasHeaders(self, message, headers):
|
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
|
Asserts that the `message` has all `headers`.
|
2013-08-20 07:04:50 +08:00
|
|
|
|
|
2016-10-27 15:53:39 +08:00
|
|
|
|
message: can be an instance of an email.Message subclass or a string
|
|
|
|
|
with the contents of an email message.
|
|
|
|
|
headers: should be a set of (header-name, header-value) tuples.
|
2013-08-20 07:04:50 +08:00
|
|
|
|
"""
|
2016-12-29 23:27:49 +08:00
|
|
|
|
if isinstance(message, bytes):
|
2013-12-31 06:45:43 +08:00
|
|
|
|
message = message_from_bytes(message)
|
|
|
|
|
msg_headers = set(message.items())
|
2013-08-20 07:04:50 +08:00
|
|
|
|
self.assertTrue(
|
|
|
|
|
headers.issubset(msg_headers),
|
|
|
|
|
msg="Message is missing "
|
|
|
|
|
"the following headers: %s" % (headers - msg_headers),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2013-08-21 18:48:16 +08:00
|
|
|
|
class MailTests(HeadersCheckMixin, SimpleTestCase):
|
2011-01-15 13:55:24 +08:00
|
|
|
|
"""
|
|
|
|
|
Non-backend specific tests.
|
|
|
|
|
"""
|
2022-02-04 03:24:19 +08:00
|
|
|
|
|
2016-08-03 21:44:35 +08:00
|
|
|
|
def get_decoded_attachments(self, django_message):
|
|
|
|
|
"""
|
|
|
|
|
Encode the specified django.core.mail.message.EmailMessage, then decode
|
|
|
|
|
it using Python's email.parser module and, for each attachment of the
|
|
|
|
|
message, return a list of tuples with (filename, content, mimetype).
|
|
|
|
|
"""
|
|
|
|
|
msg_bytes = django_message.message().as_bytes()
|
|
|
|
|
email_message = message_from_bytes(msg_bytes)
|
|
|
|
|
|
|
|
|
|
def iter_attachments():
|
|
|
|
|
for i in email_message.walk():
|
2017-02-18 08:45:34 +08:00
|
|
|
|
if i.get_content_disposition() == "attachment":
|
2016-08-03 21:44:35 +08:00
|
|
|
|
filename = i.get_filename()
|
|
|
|
|
content = i.get_payload(decode=True)
|
|
|
|
|
mimetype = i.get_content_type()
|
|
|
|
|
yield filename, content, mimetype
|
|
|
|
|
|
|
|
|
|
return list(iter_attachments())
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
|
|
|
|
def test_ascii(self):
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject", "Content", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
message = email.message()
|
2012-08-09 18:12:22 +08:00
|
|
|
|
self.assertEqual(message["Subject"], "Subject")
|
2010-10-11 23:11:55 +08:00
|
|
|
|
self.assertEqual(message.get_payload(), "Content")
|
|
|
|
|
self.assertEqual(message["From"], "from@example.com")
|
|
|
|
|
self.assertEqual(message["To"], "to@example.com")
|
|
|
|
|
|
|
|
|
|
def test_multiple_recipients(self):
|
2011-01-15 13:55:24 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com", "other@example.com"],
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
message = email.message()
|
2012-08-09 18:12:22 +08:00
|
|
|
|
self.assertEqual(message["Subject"], "Subject")
|
2010-10-11 23:11:55 +08:00
|
|
|
|
self.assertEqual(message.get_payload(), "Content")
|
|
|
|
|
self.assertEqual(message["From"], "from@example.com")
|
|
|
|
|
self.assertEqual(message["To"], "to@example.com, other@example.com")
|
|
|
|
|
|
2017-12-09 11:28:34 +08:00
|
|
|
|
def test_header_omitted_for_no_to_recipients(self):
|
|
|
|
|
message = EmailMessage(
|
|
|
|
|
"Subject", "Content", "from@example.com", cc=["cc@example.com"]
|
|
|
|
|
).message()
|
|
|
|
|
self.assertNotIn("To", message)
|
|
|
|
|
|
2016-11-10 21:07:19 +08:00
|
|
|
|
def test_recipients_with_empty_strings(self):
|
|
|
|
|
"""
|
|
|
|
|
Empty strings in various recipient arguments are always stripped
|
|
|
|
|
off the final recipient list.
|
|
|
|
|
"""
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com", ""],
|
|
|
|
|
cc=["cc@example.com", ""],
|
|
|
|
|
bcc=["", "bcc@example.com"],
|
|
|
|
|
reply_to=["", None],
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
email.recipients(), ["to@example.com", "cc@example.com", "bcc@example.com"]
|
|
|
|
|
)
|
|
|
|
|
|
2010-10-11 23:11:55 +08:00
|
|
|
|
def test_cc(self):
|
|
|
|
|
"""Regression test for #7722"""
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
cc=["cc@example.com"],
|
|
|
|
|
)
|
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["Cc"], "cc@example.com")
|
|
|
|
|
self.assertEqual(email.recipients(), ["to@example.com", "cc@example.com"])
|
|
|
|
|
|
|
|
|
|
# Test multiple CC with multiple To
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com", "other@example.com"],
|
|
|
|
|
cc=["cc@example.com", "cc.other@example.com"],
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["Cc"], "cc@example.com, cc.other@example.com")
|
2015-09-12 07:33:12 +08:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
email.recipients(),
|
|
|
|
|
[
|
|
|
|
|
"to@example.com",
|
|
|
|
|
"other@example.com",
|
|
|
|
|
"cc@example.com",
|
|
|
|
|
"cc.other@example.com",
|
2022-02-04 03:24:19 +08:00
|
|
|
|
],
|
2015-09-12 07:33:12 +08:00
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
|
|
|
|
# Testing with Bcc
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com", "other@example.com"],
|
|
|
|
|
cc=["cc@example.com", "cc.other@example.com"],
|
|
|
|
|
bcc=["bcc@example.com"],
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["Cc"], "cc@example.com, cc.other@example.com")
|
2015-09-12 07:33:12 +08:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
email.recipients(),
|
|
|
|
|
[
|
|
|
|
|
"to@example.com",
|
|
|
|
|
"other@example.com",
|
|
|
|
|
"cc@example.com",
|
|
|
|
|
"cc.other@example.com",
|
|
|
|
|
"bcc@example.com",
|
2022-02-04 03:24:19 +08:00
|
|
|
|
],
|
2015-09-12 07:33:12 +08:00
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
2017-12-09 22:42:46 +08:00
|
|
|
|
def test_cc_headers(self):
|
|
|
|
|
message = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
cc=["foo@example.com"],
|
|
|
|
|
headers={"Cc": "override@example.com"},
|
|
|
|
|
).message()
|
|
|
|
|
self.assertEqual(message["Cc"], "override@example.com")
|
|
|
|
|
|
2017-12-29 04:58:09 +08:00
|
|
|
|
def test_cc_in_headers_only(self):
|
|
|
|
|
message = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"Cc": "foo@example.com"},
|
|
|
|
|
).message()
|
|
|
|
|
self.assertEqual(message["Cc"], "foo@example.com")
|
|
|
|
|
|
2014-11-26 07:05:44 +08:00
|
|
|
|
def test_reply_to(self):
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
reply_to=["reply_to@example.com"],
|
|
|
|
|
)
|
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["Reply-To"], "reply_to@example.com")
|
|
|
|
|
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
reply_to=["reply_to1@example.com", "reply_to2@example.com"],
|
|
|
|
|
)
|
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
message["Reply-To"], "reply_to1@example.com, reply_to2@example.com"
|
|
|
|
|
)
|
|
|
|
|
|
2011-04-30 22:00:15 +08:00
|
|
|
|
def test_recipients_as_tuple(self):
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
("to@example.com", "other@example.com"),
|
|
|
|
|
cc=("cc@example.com", "cc.other@example.com"),
|
|
|
|
|
bcc=("bcc@example.com",),
|
|
|
|
|
)
|
2011-04-30 22:00:15 +08:00
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["Cc"], "cc@example.com, cc.other@example.com")
|
2015-09-12 07:33:12 +08:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
email.recipients(),
|
|
|
|
|
[
|
|
|
|
|
"to@example.com",
|
|
|
|
|
"other@example.com",
|
|
|
|
|
"cc@example.com",
|
|
|
|
|
"cc.other@example.com",
|
|
|
|
|
"bcc@example.com",
|
2022-02-04 03:24:19 +08:00
|
|
|
|
],
|
2015-09-12 07:33:12 +08:00
|
|
|
|
)
|
2011-04-30 22:00:15 +08:00
|
|
|
|
|
2014-11-27 10:16:31 +08:00
|
|
|
|
def test_recipients_as_string(self):
|
|
|
|
|
with self.assertRaisesMessage(
|
|
|
|
|
TypeError, '"to" argument must be a list or tuple'
|
|
|
|
|
):
|
|
|
|
|
EmailMessage(to="foo@example.com")
|
|
|
|
|
with self.assertRaisesMessage(
|
|
|
|
|
TypeError, '"cc" argument must be a list or tuple'
|
|
|
|
|
):
|
|
|
|
|
EmailMessage(cc="foo@example.com")
|
|
|
|
|
with self.assertRaisesMessage(
|
|
|
|
|
TypeError, '"bcc" argument must be a list or tuple'
|
|
|
|
|
):
|
|
|
|
|
EmailMessage(bcc="foo@example.com")
|
2014-11-26 07:05:44 +08:00
|
|
|
|
with self.assertRaisesMessage(
|
|
|
|
|
TypeError, '"reply_to" argument must be a list or tuple'
|
|
|
|
|
):
|
|
|
|
|
EmailMessage(reply_to="reply_to@example.com")
|
2014-11-27 10:16:31 +08:00
|
|
|
|
|
2010-10-11 23:11:55 +08:00
|
|
|
|
def test_header_injection(self):
|
2020-07-16 16:03:59 +08:00
|
|
|
|
msg = "Header values can't contain newlines "
|
2010-10-11 23:11:55 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject\nInjection Test", "Content", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
2020-07-16 16:03:59 +08:00
|
|
|
|
with self.assertRaisesMessage(BadHeaderError, msg):
|
2016-01-17 19:26:39 +08:00
|
|
|
|
email.message()
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
2017-01-27 03:58:33 +08:00
|
|
|
|
gettext_lazy("Subject\nInjection Test"),
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
2015-09-12 07:33:12 +08:00
|
|
|
|
)
|
2020-07-16 16:03:59 +08:00
|
|
|
|
with self.assertRaisesMessage(BadHeaderError, msg):
|
2016-01-17 19:26:39 +08:00
|
|
|
|
email.message()
|
2020-07-16 16:03:59 +08:00
|
|
|
|
with self.assertRaisesMessage(BadHeaderError, msg):
|
|
|
|
|
EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["Name\nInjection test <to@example.com>"],
|
|
|
|
|
).message()
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
|
|
|
|
def test_space_continuation(self):
|
|
|
|
|
"""
|
2014-03-02 22:25:53 +08:00
|
|
|
|
Test for space continuation character in long (ASCII) subject headers (#7747)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
"""
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Long subject lines that get wrapped should contain a space continuation "
|
|
|
|
|
"character to get expected behavior in Outlook and Thunderbird",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
message = email.message()
|
2015-09-12 07:33:12 +08:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
message["Subject"].encode(),
|
|
|
|
|
b"Long subject lines that get wrapped should contain a space continuation\n"
|
|
|
|
|
b" character to get expected behavior in Outlook and Thunderbird",
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
|
|
|
|
def test_message_header_overrides(self):
|
|
|
|
|
"""
|
|
|
|
|
Specifying dates or message-ids in the extra headers overrides the
|
|
|
|
|
default values (#9233)
|
|
|
|
|
"""
|
|
|
|
|
headers = {"date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"subject",
|
|
|
|
|
"content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers=headers,
|
|
|
|
|
)
|
2022-02-04 03:24:19 +08:00
|
|
|
|
|
2013-08-20 07:04:50 +08:00
|
|
|
|
self.assertMessageHasHeaders(
|
|
|
|
|
email.message(),
|
|
|
|
|
{
|
2012-09-28 15:27:38 +08:00
|
|
|
|
("Content-Transfer-Encoding", "7bit"),
|
|
|
|
|
("Content-Type", 'text/plain; charset="utf-8"'),
|
|
|
|
|
("From", "from@example.com"),
|
|
|
|
|
("MIME-Version", "1.0"),
|
|
|
|
|
("Message-ID", "foo"),
|
|
|
|
|
("Subject", "subject"),
|
|
|
|
|
("To", "to@example.com"),
|
|
|
|
|
("date", "Fri, 09 Nov 2001 01:08:47 -0000"),
|
2013-08-20 07:04:50 +08:00
|
|
|
|
},
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
|
|
|
|
def test_from_header(self):
|
|
|
|
|
"""
|
|
|
|
|
Make sure we can manually set the From header (#9214)
|
|
|
|
|
"""
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["From"], "from@example.com")
|
|
|
|
|
|
2011-12-30 20:44:35 +08:00
|
|
|
|
def test_to_header(self):
|
|
|
|
|
"""
|
|
|
|
|
Make sure we can manually set the To header (#17444)
|
|
|
|
|
"""
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["list-subscriber@example.com", "list-subscriber2@example.com"],
|
|
|
|
|
headers={"To": "mailing-list@example.com"},
|
|
|
|
|
)
|
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["To"], "mailing-list@example.com")
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
email.to, ["list-subscriber@example.com", "list-subscriber2@example.com"]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# If we don't set the To header manually, it should default to the `to`
|
|
|
|
|
# argument to the constructor.
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["list-subscriber@example.com", "list-subscriber2@example.com"],
|
|
|
|
|
)
|
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
message["To"], "list-subscriber@example.com, list-subscriber2@example.com"
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
email.to, ["list-subscriber@example.com", "list-subscriber2@example.com"]
|
|
|
|
|
)
|
|
|
|
|
|
2017-12-29 04:58:09 +08:00
|
|
|
|
def test_to_in_headers_only(self):
|
|
|
|
|
message = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
headers={"To": "to@example.com"},
|
|
|
|
|
).message()
|
|
|
|
|
self.assertEqual(message["To"], "to@example.com")
|
|
|
|
|
|
2014-11-26 07:05:44 +08:00
|
|
|
|
def test_reply_to_header(self):
|
|
|
|
|
"""
|
|
|
|
|
Specifying 'Reply-To' in headers should override reply_to.
|
|
|
|
|
"""
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
reply_to=["foo@example.com"],
|
|
|
|
|
headers={"Reply-To": "override@example.com"},
|
|
|
|
|
)
|
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["Reply-To"], "override@example.com")
|
|
|
|
|
|
2017-12-29 04:58:09 +08:00
|
|
|
|
def test_reply_to_in_headers_only(self):
|
|
|
|
|
message = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"Reply-To": "reply_to@example.com"},
|
|
|
|
|
).message()
|
|
|
|
|
self.assertEqual(message["Reply-To"], "reply_to@example.com")
|
|
|
|
|
|
2010-10-11 23:11:55 +08:00
|
|
|
|
def test_multiple_message_call(self):
|
|
|
|
|
"""
|
|
|
|
|
Regression for #13259 - Make sure that headers are not changed when
|
|
|
|
|
calling EmailMessage.message()
|
|
|
|
|
"""
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["From"], "from@example.com")
|
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["From"], "from@example.com")
|
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_unicode_address_header(self):
|
2010-10-11 23:11:55 +08:00
|
|
|
|
"""
|
2020-04-18 22:46:05 +08:00
|
|
|
|
Regression for #11144 - When a to/from/cc header contains Unicode,
|
2010-10-11 23:11:55 +08:00
|
|
|
|
make sure the email addresses are parsed correctly (especially with
|
|
|
|
|
regards to commas)
|
|
|
|
|
"""
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
['"Firstname Sürname" <to@example.com>', "other@example.com"],
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
email.message()["To"],
|
|
|
|
|
"=?utf-8?q?Firstname_S=C3=BCrname?= <to@example.com>, other@example.com",
|
|
|
|
|
)
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
['"Sürname, Firstname" <to@example.com>', "other@example.com"],
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
email.message()["To"],
|
|
|
|
|
"=?utf-8?q?S=C3=BCrname=2C_Firstname?= <to@example.com>, other@example.com",
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_unicode_headers(self):
|
2019-01-03 07:18:19 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Gżegżółka",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={
|
|
|
|
|
"Sender": '"Firstname Sürname" <sender@example.com>',
|
|
|
|
|
"Comments": "My Sürname is non-ASCII",
|
|
|
|
|
},
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
message = email.message()
|
|
|
|
|
self.assertEqual(message["Subject"], "=?utf-8?b?R8W8ZWfFvMOzxYJrYQ==?=")
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
message["Sender"], "=?utf-8?q?Firstname_S=C3=BCrname?= <sender@example.com>"
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
message["Comments"], "=?utf-8?q?My_S=C3=BCrname_is_non-ASCII?="
|
|
|
|
|
)
|
|
|
|
|
|
2010-10-11 23:11:55 +08:00
|
|
|
|
def test_safe_mime_multipart(self):
|
|
|
|
|
"""
|
|
|
|
|
Make sure headers can be set with a different encoding than utf-8 in
|
|
|
|
|
SafeMIMEMultipart as well
|
|
|
|
|
"""
|
|
|
|
|
headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
|
2014-03-31 03:11:05 +08:00
|
|
|
|
from_email, to = "from@example.com", '"Sürname, Firstname" <to@example.com>'
|
2010-10-11 23:11:55 +08:00
|
|
|
|
text_content = "This is an important message."
|
|
|
|
|
html_content = "<p>This is an <strong>important</strong> message.</p>"
|
|
|
|
|
msg = EmailMultiAlternatives(
|
|
|
|
|
"Message from Firstname Sürname",
|
|
|
|
|
text_content,
|
|
|
|
|
from_email,
|
|
|
|
|
[to],
|
|
|
|
|
headers=headers,
|
|
|
|
|
)
|
|
|
|
|
msg.attach_alternative(html_content, "text/html")
|
|
|
|
|
msg.encoding = "iso-8859-1"
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
msg.message()["To"],
|
|
|
|
|
"=?iso-8859-1?q?S=FCrname=2C_Firstname?= <to@example.com>",
|
|
|
|
|
)
|
2012-08-09 18:12:22 +08:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
msg.message()["Subject"],
|
|
|
|
|
"=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=",
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
2017-08-28 12:28:18 +08:00
|
|
|
|
def test_safe_mime_multipart_with_attachments(self):
|
|
|
|
|
"""
|
|
|
|
|
EmailMultiAlternatives includes alternatives if the body is empty and
|
|
|
|
|
it has attachments.
|
|
|
|
|
"""
|
|
|
|
|
msg = EmailMultiAlternatives(body="")
|
|
|
|
|
html_content = "<p>This is <strong>html</strong></p>"
|
|
|
|
|
msg.attach_alternative(html_content, "text/html")
|
|
|
|
|
msg.attach("example.txt", "Text file content", "text/plain")
|
|
|
|
|
self.assertIn(html_content, msg.message().as_string())
|
|
|
|
|
|
2018-02-19 21:39:53 +08:00
|
|
|
|
def test_none_body(self):
|
|
|
|
|
msg = EmailMessage("subject", None, "from@example.com", ["to@example.com"])
|
|
|
|
|
self.assertEqual(msg.body, "")
|
|
|
|
|
self.assertEqual(msg.message().get_payload(), "")
|
|
|
|
|
|
2019-07-02 20:15:32 +08:00
|
|
|
|
@mock.patch("socket.getfqdn", return_value="漢字")
|
|
|
|
|
def test_non_ascii_dns_non_unicode_email(self, mocked_getfqdn):
|
|
|
|
|
delattr(DNS_NAME, "_fqdn")
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"subject", "content", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
email.encoding = "iso-8859-1"
|
|
|
|
|
self.assertIn("@xn--p8s937b>", email.message()["Message-ID"])
|
|
|
|
|
|
2010-10-11 23:11:55 +08:00
|
|
|
|
def test_encoding(self):
|
|
|
|
|
"""
|
|
|
|
|
Regression for #12791 - Encode body correctly with other encodings
|
|
|
|
|
than utf-8
|
|
|
|
|
"""
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Firstname Sürname is a great guy.",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["other@example.com"],
|
|
|
|
|
)
|
|
|
|
|
email.encoding = "iso-8859-1"
|
|
|
|
|
message = email.message()
|
2013-08-20 07:04:50 +08:00
|
|
|
|
self.assertMessageHasHeaders(
|
|
|
|
|
message,
|
|
|
|
|
{
|
|
|
|
|
("MIME-Version", "1.0"),
|
|
|
|
|
("Content-Type", 'text/plain; charset="iso-8859-1"'),
|
|
|
|
|
("Content-Transfer-Encoding", "quoted-printable"),
|
|
|
|
|
("Subject", "Subject"),
|
|
|
|
|
("From", "from@example.com"),
|
|
|
|
|
("To", "other@example.com"),
|
2022-02-04 03:24:19 +08:00
|
|
|
|
},
|
2013-08-20 07:04:50 +08:00
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
self.assertEqual(message.get_payload(), "Firstname S=FCrname is a great guy.")
|
|
|
|
|
|
|
|
|
|
# MIME attachments works correctly with other encodings than utf-8.
|
|
|
|
|
text_content = "Firstname Sürname is a great guy."
|
|
|
|
|
html_content = "<p>Firstname Sürname is a <strong>great</strong> guy.</p>"
|
|
|
|
|
msg = EmailMultiAlternatives(
|
|
|
|
|
"Subject", text_content, "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
msg.encoding = "iso-8859-1"
|
|
|
|
|
msg.attach_alternative(html_content, "text/html")
|
2013-08-20 07:04:50 +08:00
|
|
|
|
payload0 = msg.message().get_payload(0)
|
|
|
|
|
self.assertMessageHasHeaders(
|
|
|
|
|
payload0,
|
|
|
|
|
{
|
|
|
|
|
("MIME-Version", "1.0"),
|
|
|
|
|
("Content-Type", 'text/plain; charset="iso-8859-1"'),
|
|
|
|
|
("Content-Transfer-Encoding", "quoted-printable"),
|
2022-02-04 03:24:19 +08:00
|
|
|
|
},
|
2013-08-20 07:04:50 +08:00
|
|
|
|
)
|
2013-12-29 01:35:17 +08:00
|
|
|
|
self.assertTrue(
|
|
|
|
|
payload0.as_bytes().endswith(b"\n\nFirstname S=FCrname is a great guy.")
|
2022-02-04 03:24:19 +08:00
|
|
|
|
)
|
2013-08-20 07:04:50 +08:00
|
|
|
|
payload1 = msg.message().get_payload(1)
|
|
|
|
|
self.assertMessageHasHeaders(
|
|
|
|
|
payload1,
|
|
|
|
|
{
|
|
|
|
|
("MIME-Version", "1.0"),
|
|
|
|
|
("Content-Type", 'text/html; charset="iso-8859-1"'),
|
|
|
|
|
("Content-Transfer-Encoding", "quoted-printable"),
|
2022-02-04 03:24:19 +08:00
|
|
|
|
},
|
2013-08-20 07:04:50 +08:00
|
|
|
|
)
|
2015-09-12 07:33:12 +08:00
|
|
|
|
self.assertTrue(
|
|
|
|
|
payload1.as_bytes().endswith(
|
|
|
|
|
b"\n\n<p>Firstname S=FCrname is a <strong>great</strong> guy.</p>"
|
|
|
|
|
)
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
|
|
|
|
def test_attachments(self):
|
|
|
|
|
"""Regression test for #9367"""
|
|
|
|
|
headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
|
|
|
|
|
subject, from_email, to = "hello", "from@example.com", "to@example.com"
|
|
|
|
|
text_content = "This is an important message."
|
|
|
|
|
html_content = "<p>This is an <strong>important</strong> message.</p>"
|
|
|
|
|
msg = EmailMultiAlternatives(
|
|
|
|
|
subject, text_content, from_email, [to], headers=headers
|
|
|
|
|
)
|
|
|
|
|
msg.attach_alternative(html_content, "text/html")
|
2012-05-19 23:43:34 +08:00
|
|
|
|
msg.attach("an attachment.pdf", b"%PDF-1.4.%...", mimetype="application/pdf")
|
2013-12-29 01:35:17 +08:00
|
|
|
|
msg_bytes = msg.message().as_bytes()
|
|
|
|
|
message = message_from_bytes(msg_bytes)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
self.assertTrue(message.is_multipart())
|
|
|
|
|
self.assertEqual(message.get_content_type(), "multipart/mixed")
|
|
|
|
|
self.assertEqual(message.get_default_type(), "text/plain")
|
|
|
|
|
payload = message.get_payload()
|
|
|
|
|
self.assertEqual(payload[0].get_content_type(), "multipart/alternative")
|
|
|
|
|
self.assertEqual(payload[1].get_content_type(), "application/pdf")
|
|
|
|
|
|
2017-04-07 18:23:25 +08:00
|
|
|
|
def test_attachments_two_tuple(self):
|
|
|
|
|
msg = EmailMessage(attachments=[("filename1", "content1")])
|
|
|
|
|
filename, content, mimetype = self.get_decoded_attachments(msg)[0]
|
|
|
|
|
self.assertEqual(filename, "filename1")
|
|
|
|
|
self.assertEqual(content, b"content1")
|
|
|
|
|
self.assertEqual(mimetype, "application/octet-stream")
|
|
|
|
|
|
2017-04-08 05:54:50 +08:00
|
|
|
|
def test_attachments_MIMEText(self):
|
|
|
|
|
txt = MIMEText("content1")
|
|
|
|
|
msg = EmailMessage(attachments=[txt])
|
|
|
|
|
payload = msg.message().get_payload()
|
|
|
|
|
self.assertEqual(payload[0], txt)
|
|
|
|
|
|
2012-01-15 10:33:31 +08:00
|
|
|
|
def test_non_ascii_attachment_filename(self):
|
|
|
|
|
"""Regression test for #14964"""
|
|
|
|
|
headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
|
|
|
|
|
subject, from_email, to = "hello", "from@example.com", "to@example.com"
|
|
|
|
|
content = "This is the message."
|
|
|
|
|
msg = EmailMessage(subject, content, from_email, [to], headers=headers)
|
|
|
|
|
# Unicode in file name
|
2012-06-08 00:08:47 +08:00
|
|
|
|
msg.attach("une pièce jointe.pdf", b"%PDF-1.4.%...", mimetype="application/pdf")
|
2013-12-29 01:35:17 +08:00
|
|
|
|
msg_bytes = msg.message().as_bytes()
|
|
|
|
|
message = message_from_bytes(msg_bytes)
|
2012-01-15 10:33:31 +08:00
|
|
|
|
payload = message.get_payload()
|
2012-06-08 00:08:47 +08:00
|
|
|
|
self.assertEqual(payload[1].get_filename(), "une pièce jointe.pdf")
|
2012-01-15 10:33:31 +08:00
|
|
|
|
|
2015-07-22 23:47:32 +08:00
|
|
|
|
def test_attach_file(self):
|
|
|
|
|
"""
|
|
|
|
|
Test attaching a file against different mimetypes and make sure that
|
|
|
|
|
a file will be attached and sent properly even if an invalid mimetype
|
|
|
|
|
is specified.
|
|
|
|
|
"""
|
|
|
|
|
files = (
|
|
|
|
|
# filename, actual mimetype
|
|
|
|
|
("file.txt", "text/plain"),
|
|
|
|
|
("file.png", "image/png"),
|
|
|
|
|
("file_txt", None),
|
|
|
|
|
("file_png", None),
|
|
|
|
|
("file_txt.png", "image/png"),
|
|
|
|
|
("file_png.txt", "text/plain"),
|
2017-04-01 21:08:21 +08:00
|
|
|
|
("file.eml", "message/rfc822"),
|
2015-07-22 23:47:32 +08:00
|
|
|
|
)
|
|
|
|
|
test_mimetypes = ["text/plain", "image/png", None]
|
|
|
|
|
|
|
|
|
|
for basename, real_mimetype in files:
|
|
|
|
|
for mimetype in test_mimetypes:
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"subject", "body", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(mimetypes.guess_type(basename)[0], real_mimetype)
|
|
|
|
|
self.assertEqual(email.attachments, [])
|
2017-01-20 21:01:02 +08:00
|
|
|
|
file_path = os.path.join(
|
|
|
|
|
os.path.dirname(__file__), "attachments", basename
|
|
|
|
|
)
|
2015-07-22 23:47:32 +08:00
|
|
|
|
email.attach_file(file_path, mimetype=mimetype)
|
|
|
|
|
self.assertEqual(len(email.attachments), 1)
|
|
|
|
|
self.assertIn(basename, email.attachments[0])
|
|
|
|
|
msgs_sent_num = email.send()
|
|
|
|
|
self.assertEqual(msgs_sent_num, 1)
|
|
|
|
|
|
2016-07-02 04:01:58 +08:00
|
|
|
|
def test_attach_text_as_bytes(self):
|
|
|
|
|
msg = EmailMessage("subject", "body", "from@example.com", ["to@example.com"])
|
2016-08-03 21:44:35 +08:00
|
|
|
|
msg.attach("file.txt", b"file content")
|
2016-07-02 04:01:58 +08:00
|
|
|
|
sent_num = msg.send()
|
|
|
|
|
self.assertEqual(sent_num, 1)
|
2016-08-03 21:44:35 +08:00
|
|
|
|
filename, content, mimetype = self.get_decoded_attachments(msg)[0]
|
|
|
|
|
self.assertEqual(filename, "file.txt")
|
|
|
|
|
self.assertEqual(content, b"file content")
|
|
|
|
|
self.assertEqual(mimetype, "text/plain")
|
2016-07-02 04:01:58 +08:00
|
|
|
|
|
2016-08-03 21:53:06 +08:00
|
|
|
|
def test_attach_utf8_text_as_bytes(self):
|
|
|
|
|
"""
|
|
|
|
|
Non-ASCII characters encoded as valid UTF-8 are correctly transported
|
|
|
|
|
and decoded.
|
|
|
|
|
"""
|
|
|
|
|
msg = EmailMessage("subject", "body", "from@example.com", ["to@example.com"])
|
|
|
|
|
msg.attach("file.txt", b"\xc3\xa4") # UTF-8 encoded a umlaut.
|
|
|
|
|
filename, content, mimetype = self.get_decoded_attachments(msg)[0]
|
|
|
|
|
self.assertEqual(filename, "file.txt")
|
|
|
|
|
self.assertEqual(content, b"\xc3\xa4")
|
|
|
|
|
self.assertEqual(mimetype, "text/plain")
|
|
|
|
|
|
|
|
|
|
def test_attach_non_utf8_text_as_bytes(self):
|
|
|
|
|
"""
|
|
|
|
|
Binary data that can't be decoded as UTF-8 overrides the MIME type
|
|
|
|
|
instead of decoding the data.
|
|
|
|
|
"""
|
|
|
|
|
msg = EmailMessage("subject", "body", "from@example.com", ["to@example.com"])
|
|
|
|
|
msg.attach("file.txt", b"\xff") # Invalid UTF-8.
|
|
|
|
|
filename, content, mimetype = self.get_decoded_attachments(msg)[0]
|
|
|
|
|
self.assertEqual(filename, "file.txt")
|
|
|
|
|
# Content should be passed through unmodified.
|
|
|
|
|
self.assertEqual(content, b"\xff")
|
|
|
|
|
self.assertEqual(mimetype, "application/octet-stream")
|
|
|
|
|
|
2021-03-16 23:41:27 +08:00
|
|
|
|
def test_attach_mimetext_content_mimetype(self):
|
|
|
|
|
email_msg = EmailMessage()
|
|
|
|
|
txt = MIMEText("content")
|
|
|
|
|
msg = (
|
|
|
|
|
"content and mimetype must not be given when a MIMEBase instance "
|
|
|
|
|
"is provided."
|
|
|
|
|
)
|
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
|
email_msg.attach(txt, content="content")
|
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
|
email_msg.attach(txt, mimetype="text/plain")
|
|
|
|
|
|
|
|
|
|
def test_attach_content_none(self):
|
|
|
|
|
email_msg = EmailMessage()
|
|
|
|
|
msg = "content must be provided."
|
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
|
email_msg.attach("file.txt", mimetype="application/pdf")
|
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_dummy_backend(self):
|
2010-10-11 23:11:55 +08:00
|
|
|
|
"""
|
|
|
|
|
Make sure that dummy backends returns correct number of sent messages
|
|
|
|
|
"""
|
|
|
|
|
connection = dummy.EmailBackend()
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
self.assertEqual(connection.send_messages([email, email, email]), 3)
|
|
|
|
|
|
|
|
|
|
def test_arbitrary_keyword(self):
|
|
|
|
|
"""
|
|
|
|
|
Make sure that get_connection() accepts arbitrary keyword that might be
|
|
|
|
|
used with custom backends.
|
|
|
|
|
"""
|
|
|
|
|
c = mail.get_connection(fail_silently=True, foo="bar")
|
|
|
|
|
self.assertTrue(c.fail_silently)
|
|
|
|
|
|
|
|
|
|
def test_custom_backend(self):
|
|
|
|
|
"""Test custom backend defined in this suite."""
|
2013-02-26 20:19:18 +08:00
|
|
|
|
conn = mail.get_connection("mail.custombackend.EmailBackend")
|
2010-10-11 23:11:55 +08:00
|
|
|
|
self.assertTrue(hasattr(conn, "test_outbox"))
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
conn.send_messages([email])
|
|
|
|
|
self.assertEqual(len(conn.test_outbox), 1)
|
|
|
|
|
|
|
|
|
|
def test_backend_arg(self):
|
|
|
|
|
"""Test backend argument of mail.get_connection()"""
|
2015-09-12 07:33:12 +08:00
|
|
|
|
self.assertIsInstance(
|
2013-05-21 17:42:15 +08:00
|
|
|
|
mail.get_connection("django.core.mail.backends.smtp.EmailBackend"),
|
|
|
|
|
smtp.EmailBackend,
|
2022-02-04 03:24:19 +08:00
|
|
|
|
)
|
2015-09-12 07:33:12 +08:00
|
|
|
|
self.assertIsInstance(
|
|
|
|
|
mail.get_connection("django.core.mail.backends.locmem.EmailBackend"),
|
|
|
|
|
locmem.EmailBackend,
|
|
|
|
|
)
|
|
|
|
|
self.assertIsInstance(
|
2013-05-21 17:42:15 +08:00
|
|
|
|
mail.get_connection("django.core.mail.backends.dummy.EmailBackend"),
|
|
|
|
|
dummy.EmailBackend,
|
2022-02-04 03:24:19 +08:00
|
|
|
|
)
|
2015-09-12 07:33:12 +08:00
|
|
|
|
self.assertIsInstance(
|
|
|
|
|
mail.get_connection("django.core.mail.backends.console.EmailBackend"),
|
|
|
|
|
console.EmailBackend,
|
|
|
|
|
)
|
2017-01-27 02:54:16 +08:00
|
|
|
|
with tempfile.TemporaryDirectory() as tmp_dir:
|
2015-09-12 07:33:12 +08:00
|
|
|
|
self.assertIsInstance(
|
|
|
|
|
mail.get_connection(
|
|
|
|
|
"django.core.mail.backends.filebased.EmailBackend",
|
|
|
|
|
file_path=tmp_dir,
|
|
|
|
|
),
|
|
|
|
|
filebased.EmailBackend,
|
|
|
|
|
)
|
2019-11-06 16:33:07 +08:00
|
|
|
|
|
2022-02-20 03:36:01 +08:00
|
|
|
|
if sys.platform == "win32" and not PY311:
|
2019-11-06 21:34:44 +08:00
|
|
|
|
msg = (
|
|
|
|
|
"_getfullpathname: path should be string, bytes or os.PathLike, not "
|
|
|
|
|
"object"
|
2022-02-04 15:08:27 +08:00
|
|
|
|
)
|
2019-11-06 21:34:44 +08:00
|
|
|
|
else:
|
|
|
|
|
msg = "expected str, bytes or os.PathLike object, not object"
|
2019-11-06 16:33:07 +08:00
|
|
|
|
with self.assertRaisesMessage(TypeError, msg):
|
|
|
|
|
mail.get_connection(
|
|
|
|
|
"django.core.mail.backends.filebased.EmailBackend", file_path=object()
|
2022-02-04 03:24:19 +08:00
|
|
|
|
)
|
2013-05-21 17:42:15 +08:00
|
|
|
|
self.assertIsInstance(mail.get_connection(), locmem.EmailBackend)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
2011-05-18 20:08:53 +08:00
|
|
|
|
@override_settings(
|
2011-01-15 13:55:24 +08:00
|
|
|
|
EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend",
|
|
|
|
|
ADMINS=[("nobody", "nobody@example.com")],
|
|
|
|
|
MANAGERS=[("nobody", "nobody@example.com")],
|
|
|
|
|
)
|
2010-10-11 23:11:55 +08:00
|
|
|
|
def test_connection_arg(self):
|
|
|
|
|
"""Test connection argument to send_mail(), et. al."""
|
|
|
|
|
mail.outbox = []
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
# Send using non-default connection
|
2013-02-26 20:19:18 +08:00
|
|
|
|
connection = mail.get_connection("mail.custombackend.EmailBackend")
|
2010-10-11 23:11:55 +08:00
|
|
|
|
send_mail(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
connection=connection,
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
self.assertEqual(mail.outbox, [])
|
|
|
|
|
self.assertEqual(len(connection.test_outbox), 1)
|
|
|
|
|
self.assertEqual(connection.test_outbox[0].subject, "Subject")
|
2022-02-04 03:24:19 +08:00
|
|
|
|
|
2013-02-26 20:19:18 +08:00
|
|
|
|
connection = mail.get_connection("mail.custombackend.EmailBackend")
|
2010-10-11 23:11:55 +08:00
|
|
|
|
send_mass_mail(
|
|
|
|
|
[
|
2013-10-20 07:33:10 +08:00
|
|
|
|
("Subject1", "Content1", "from1@example.com", ["to1@example.com"]),
|
|
|
|
|
("Subject2", "Content2", "from2@example.com", ["to2@example.com"]),
|
2013-10-18 17:02:43 +08:00
|
|
|
|
],
|
|
|
|
|
connection=connection,
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
self.assertEqual(mail.outbox, [])
|
|
|
|
|
self.assertEqual(len(connection.test_outbox), 2)
|
|
|
|
|
self.assertEqual(connection.test_outbox[0].subject, "Subject1")
|
|
|
|
|
self.assertEqual(connection.test_outbox[1].subject, "Subject2")
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
2013-02-26 20:19:18 +08:00
|
|
|
|
connection = mail.get_connection("mail.custombackend.EmailBackend")
|
2011-01-15 13:55:24 +08:00
|
|
|
|
mail_admins("Admin message", "Content", connection=connection)
|
|
|
|
|
self.assertEqual(mail.outbox, [])
|
|
|
|
|
self.assertEqual(len(connection.test_outbox), 1)
|
|
|
|
|
self.assertEqual(connection.test_outbox[0].subject, "[Django] Admin message")
|
2010-10-11 23:11:55 +08:00
|
|
|
|
|
2013-02-26 20:19:18 +08:00
|
|
|
|
connection = mail.get_connection("mail.custombackend.EmailBackend")
|
2011-01-15 13:55:24 +08:00
|
|
|
|
mail_managers("Manager message", "Content", connection=connection)
|
|
|
|
|
self.assertEqual(mail.outbox, [])
|
|
|
|
|
self.assertEqual(len(connection.test_outbox), 1)
|
|
|
|
|
self.assertEqual(connection.test_outbox[0].subject, "[Django] Manager message")
|
|
|
|
|
|
2011-02-28 10:42:28 +08:00
|
|
|
|
def test_dont_mangle_from_in_body(self):
|
|
|
|
|
# Regression for #13433 - Make sure that EmailMessage doesn't mangle
|
|
|
|
|
# 'From ' in message body.
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"From the future",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2014-10-28 18:02:56 +08:00
|
|
|
|
self.assertNotIn(b">From the future", email.message().as_bytes())
|
2011-02-28 10:42:28 +08:00
|
|
|
|
|
2011-05-08 00:59:33 +08:00
|
|
|
|
def test_dont_base64_encode(self):
|
|
|
|
|
# Ticket #3472
|
|
|
|
|
# Shouldn't use Base64 encoding at all
|
2015-09-12 07:33:12 +08:00
|
|
|
|
msg = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"UTF-8 encoded body",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2016-10-13 02:23:37 +08:00
|
|
|
|
self.assertIn(b"Content-Transfer-Encoding: 7bit", msg.message().as_bytes())
|
2011-05-08 00:59:33 +08:00
|
|
|
|
|
|
|
|
|
# Ticket #11212
|
|
|
|
|
# Shouldn't use quoted printable, should detect it can represent
|
|
|
|
|
# content with 7 bit data.
|
2015-09-12 07:33:12 +08:00
|
|
|
|
msg = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Body with only ASCII characters.",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2013-12-29 01:35:17 +08:00
|
|
|
|
s = msg.message().as_bytes()
|
2014-10-28 18:02:56 +08:00
|
|
|
|
self.assertIn(b"Content-Transfer-Encoding: 7bit", s)
|
2011-05-08 00:59:33 +08:00
|
|
|
|
|
|
|
|
|
# Shouldn't use quoted printable, should detect it can represent
|
|
|
|
|
# content with 8 bit data.
|
2015-09-12 07:33:12 +08:00
|
|
|
|
msg = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Body with latin characters: àáä.",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2013-12-29 01:35:17 +08:00
|
|
|
|
s = msg.message().as_bytes()
|
2014-10-28 18:02:56 +08:00
|
|
|
|
self.assertIn(b"Content-Transfer-Encoding: 8bit", s)
|
2016-10-12 02:53:26 +08:00
|
|
|
|
s = msg.message().as_string()
|
2017-01-20 17:20:53 +08:00
|
|
|
|
self.assertIn("Content-Transfer-Encoding: 8bit", s)
|
2011-05-08 00:59:33 +08:00
|
|
|
|
|
2015-09-12 07:33:12 +08:00
|
|
|
|
msg = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Body with non latin characters: А Б В Г Д Е Ж Ѕ З И І К Л М Н О П.",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2013-12-29 01:35:17 +08:00
|
|
|
|
s = msg.message().as_bytes()
|
2014-10-28 18:02:56 +08:00
|
|
|
|
self.assertIn(b"Content-Transfer-Encoding: 8bit", s)
|
2016-10-12 02:53:26 +08:00
|
|
|
|
s = msg.message().as_string()
|
2017-01-20 17:20:53 +08:00
|
|
|
|
self.assertIn("Content-Transfer-Encoding: 8bit", s)
|
2011-05-08 00:59:33 +08:00
|
|
|
|
|
2013-08-21 09:17:26 +08:00
|
|
|
|
def test_dont_base64_encode_message_rfc822(self):
|
|
|
|
|
# Ticket #18967
|
|
|
|
|
# Shouldn't use base64 encoding for a child EmailMessage attachment.
|
|
|
|
|
# Create a child message first
|
2015-09-12 07:33:12 +08:00
|
|
|
|
child_msg = EmailMessage(
|
|
|
|
|
"Child Subject",
|
|
|
|
|
"Some body of child message",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2013-08-21 09:17:26 +08:00
|
|
|
|
child_s = child_msg.message().as_string()
|
|
|
|
|
|
|
|
|
|
# Now create a parent
|
2015-09-12 07:33:12 +08:00
|
|
|
|
parent_msg = EmailMessage(
|
|
|
|
|
"Parent Subject",
|
|
|
|
|
"Some parent body",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2013-08-21 09:17:26 +08:00
|
|
|
|
|
|
|
|
|
# Attach to parent as a string
|
|
|
|
|
parent_msg.attach(content=child_s, mimetype="message/rfc822")
|
|
|
|
|
parent_s = parent_msg.message().as_string()
|
|
|
|
|
|
2016-10-27 15:53:39 +08:00
|
|
|
|
# The child message header is not base64 encoded
|
2017-01-20 17:20:53 +08:00
|
|
|
|
self.assertIn("Child Subject", parent_s)
|
2013-08-21 09:17:26 +08:00
|
|
|
|
|
|
|
|
|
# Feature test: try attaching email.Message object directly to the mail.
|
2015-09-12 07:33:12 +08:00
|
|
|
|
parent_msg = EmailMessage(
|
|
|
|
|
"Parent Subject",
|
|
|
|
|
"Some parent body",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2013-08-21 09:17:26 +08:00
|
|
|
|
parent_msg.attach(content=child_msg.message(), mimetype="message/rfc822")
|
|
|
|
|
parent_s = parent_msg.message().as_string()
|
|
|
|
|
|
2016-10-27 15:53:39 +08:00
|
|
|
|
# The child message header is not base64 encoded
|
2017-01-20 17:20:53 +08:00
|
|
|
|
self.assertIn("Child Subject", parent_s)
|
2013-08-21 09:17:26 +08:00
|
|
|
|
|
|
|
|
|
# Feature test: try attaching Django's EmailMessage object directly to the mail.
|
2015-09-12 07:33:12 +08:00
|
|
|
|
parent_msg = EmailMessage(
|
|
|
|
|
"Parent Subject",
|
|
|
|
|
"Some parent body",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2013-08-21 09:17:26 +08:00
|
|
|
|
parent_msg.attach(content=child_msg, mimetype="message/rfc822")
|
|
|
|
|
parent_s = parent_msg.message().as_string()
|
|
|
|
|
|
2016-10-27 15:53:39 +08:00
|
|
|
|
# The child message header is not base64 encoded
|
2017-01-20 17:20:53 +08:00
|
|
|
|
self.assertIn("Child Subject", parent_s)
|
2013-08-21 09:17:26 +08:00
|
|
|
|
|
2018-10-23 03:21:33 +08:00
|
|
|
|
def test_custom_utf8_encoding(self):
|
|
|
|
|
"""A UTF-8 charset with a custom body encoding is respected."""
|
|
|
|
|
body = "Body with latin characters: àáä."
|
|
|
|
|
msg = EmailMessage("Subject", body, "bounce@example.com", ["to@example.com"])
|
|
|
|
|
encoding = charset.Charset("utf-8")
|
|
|
|
|
encoding.body_encoding = charset.QP
|
|
|
|
|
msg.encoding = encoding
|
|
|
|
|
message = msg.message()
|
|
|
|
|
self.assertMessageHasHeaders(
|
|
|
|
|
message,
|
|
|
|
|
{
|
|
|
|
|
("MIME-Version", "1.0"),
|
|
|
|
|
("Content-Type", 'text/plain; charset="utf-8"'),
|
|
|
|
|
("Content-Transfer-Encoding", "quoted-printable"),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(message.get_payload(), encoding.body_encode(body))
|
|
|
|
|
|
2016-04-02 17:41:47 +08:00
|
|
|
|
def test_sanitize_address(self):
|
2019-04-30 00:48:20 +08:00
|
|
|
|
"""Email addresses are properly sanitized."""
|
|
|
|
|
for email_address, encoding, expected_result in (
|
|
|
|
|
# ASCII addresses.
|
|
|
|
|
("to@example.com", "ascii", "to@example.com"),
|
|
|
|
|
("to@example.com", "utf-8", "to@example.com"),
|
|
|
|
|
(("A name", "to@example.com"), "ascii", "A name <to@example.com>"),
|
|
|
|
|
(
|
|
|
|
|
("A name", "to@example.com"),
|
|
|
|
|
"utf-8",
|
2020-07-15 13:30:15 +08:00
|
|
|
|
"A name <to@example.com>",
|
2019-04-30 00:48:20 +08:00
|
|
|
|
),
|
2019-06-13 22:39:00 +08:00
|
|
|
|
("localpartonly", "ascii", "localpartonly"),
|
2019-06-06 20:47:03 +08:00
|
|
|
|
# ASCII addresses with display names.
|
|
|
|
|
("A name <to@example.com>", "ascii", "A name <to@example.com>"),
|
2020-07-15 13:30:15 +08:00
|
|
|
|
("A name <to@example.com>", "utf-8", "A name <to@example.com>"),
|
2019-06-06 20:47:03 +08:00
|
|
|
|
('"A name" <to@example.com>', "ascii", "A name <to@example.com>"),
|
2020-07-15 13:30:15 +08:00
|
|
|
|
('"A name" <to@example.com>', "utf-8", "A name <to@example.com>"),
|
2019-04-30 00:48:20 +08:00
|
|
|
|
# Unicode addresses (supported per RFC-6532).
|
|
|
|
|
("tó@example.com", "utf-8", "=?utf-8?b?dMOz?=@example.com"),
|
2019-06-06 20:47:03 +08:00
|
|
|
|
("to@éxample.com", "utf-8", "to@xn--xample-9ua.com"),
|
2019-04-30 00:48:20 +08:00
|
|
|
|
(
|
|
|
|
|
("Tó Example", "tó@example.com"),
|
|
|
|
|
"utf-8",
|
|
|
|
|
"=?utf-8?q?T=C3=B3_Example?= <=?utf-8?b?dMOz?=@example.com>",
|
|
|
|
|
),
|
2019-06-06 20:47:03 +08:00
|
|
|
|
# Unicode addresses with display names.
|
|
|
|
|
(
|
|
|
|
|
"Tó Example <tó@example.com>",
|
|
|
|
|
"utf-8",
|
|
|
|
|
"=?utf-8?q?T=C3=B3_Example?= <=?utf-8?b?dMOz?=@example.com>",
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"To Example <to@éxample.com>",
|
2022-02-04 03:24:19 +08:00
|
|
|
|
"ascii",
|
2019-06-06 20:47:03 +08:00
|
|
|
|
"To Example <to@xn--xample-9ua.com>",
|
2022-02-04 03:24:19 +08:00
|
|
|
|
),
|
|
|
|
|
(
|
2019-06-06 20:47:03 +08:00
|
|
|
|
"To Example <to@éxample.com>",
|
|
|
|
|
"utf-8",
|
2020-07-15 13:30:15 +08:00
|
|
|
|
"To Example <to@xn--xample-9ua.com>",
|
2019-06-06 20:47:03 +08:00
|
|
|
|
),
|
|
|
|
|
# Addresses with two @ signs.
|
|
|
|
|
('"to@other.com"@example.com', "utf-8", r'"to@other.com"@example.com'),
|
|
|
|
|
(
|
|
|
|
|
'"to@other.com" <to@example.com>',
|
|
|
|
|
"utf-8",
|
2020-07-15 13:30:15 +08:00
|
|
|
|
'"to@other.com" <to@example.com>',
|
2019-06-06 20:47:03 +08:00
|
|
|
|
),
|
2019-04-30 00:48:20 +08:00
|
|
|
|
(
|
|
|
|
|
("To Example", "to@other.com@example.com"),
|
|
|
|
|
"utf-8",
|
2020-07-15 13:30:15 +08:00
|
|
|
|
'To Example <"to@other.com"@example.com>',
|
|
|
|
|
),
|
|
|
|
|
# Addresses with long unicode display names.
|
|
|
|
|
(
|
|
|
|
|
"Tó Example very long" * 4 + " <to@example.com>",
|
|
|
|
|
"utf-8",
|
|
|
|
|
"=?utf-8?q?T=C3=B3_Example_very_longT=C3=B3_Example_very_longT"
|
|
|
|
|
"=C3=B3_Example_?=\n"
|
|
|
|
|
" =?utf-8?q?very_longT=C3=B3_Example_very_long?= "
|
|
|
|
|
"<to@example.com>",
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
("Tó Example very long" * 4, "to@example.com"),
|
|
|
|
|
"utf-8",
|
|
|
|
|
"=?utf-8?q?T=C3=B3_Example_very_longT=C3=B3_Example_very_longT"
|
|
|
|
|
"=C3=B3_Example_?=\n"
|
|
|
|
|
" =?utf-8?q?very_longT=C3=B3_Example_very_long?= "
|
|
|
|
|
"<to@example.com>",
|
2019-04-30 00:48:20 +08:00
|
|
|
|
),
|
2020-07-15 13:30:15 +08:00
|
|
|
|
# Address with long display name and unicode domain.
|
|
|
|
|
(
|
|
|
|
|
("To Example very long" * 4, "to@exampl€.com"),
|
|
|
|
|
"utf-8",
|
|
|
|
|
"To Example very longTo Example very longTo Example very longT"
|
|
|
|
|
"o Example very\n"
|
|
|
|
|
" long <to@xn--exampl-nc1c.com>",
|
|
|
|
|
),
|
2019-04-30 00:48:20 +08:00
|
|
|
|
):
|
|
|
|
|
with self.subTest(email_address=email_address, encoding=encoding):
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
sanitize_address(email_address, encoding), expected_result
|
|
|
|
|
)
|
2016-04-02 17:41:47 +08:00
|
|
|
|
|
2019-04-30 00:48:20 +08:00
|
|
|
|
def test_sanitize_address_invalid(self):
|
|
|
|
|
for email_address in (
|
|
|
|
|
# Invalid address with two @ signs.
|
|
|
|
|
"to@other.com@example.com",
|
|
|
|
|
# Invalid address without the quotes.
|
|
|
|
|
"to@other.com <to@example.com>",
|
|
|
|
|
# Other invalid addresses.
|
|
|
|
|
"@",
|
|
|
|
|
"to@",
|
|
|
|
|
"@example.com",
|
|
|
|
|
):
|
|
|
|
|
with self.subTest(email_address=email_address):
|
|
|
|
|
with self.assertRaises(ValueError):
|
|
|
|
|
sanitize_address(email_address, encoding="utf-8")
|
|
|
|
|
|
2020-07-15 13:30:15 +08:00
|
|
|
|
def test_sanitize_address_header_injection(self):
|
|
|
|
|
msg = "Invalid address; address parts cannot contain newlines."
|
|
|
|
|
tests = [
|
|
|
|
|
"Name\nInjection <to@example.com>",
|
|
|
|
|
("Name\nInjection", "to@xample.com"),
|
|
|
|
|
"Name <to\ninjection@example.com>",
|
|
|
|
|
("Name", "to\ninjection@example.com"),
|
|
|
|
|
]
|
|
|
|
|
for email_address in tests:
|
|
|
|
|
with self.subTest(email_address=email_address):
|
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
|
sanitize_address(email_address, encoding="utf-8")
|
|
|
|
|
|
2021-03-16 23:41:27 +08:00
|
|
|
|
def test_email_multi_alternatives_content_mimetype_none(self):
|
|
|
|
|
email_msg = EmailMultiAlternatives()
|
|
|
|
|
msg = "Both content and mimetype must be provided."
|
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
|
email_msg.attach_alternative(None, "text/html")
|
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
|
email_msg.attach_alternative("<p>content</p>", None)
|
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
2016-06-03 07:41:13 +08:00
|
|
|
|
@requires_tz_support
|
2016-06-06 11:47:17 +08:00
|
|
|
|
class MailTimeZoneTests(SimpleTestCase):
|
2016-06-03 07:41:13 +08:00
|
|
|
|
@override_settings(
|
|
|
|
|
EMAIL_USE_LOCALTIME=False, USE_TZ=True, TIME_ZONE="Africa/Algiers"
|
|
|
|
|
)
|
|
|
|
|
def test_date_header_utc(self):
|
|
|
|
|
"""
|
|
|
|
|
EMAIL_USE_LOCALTIME=False creates a datetime in UTC.
|
|
|
|
|
"""
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject", "Body", "bounce@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
self.assertTrue(email.message()["Date"].endswith("-0000"))
|
|
|
|
|
|
|
|
|
|
@override_settings(
|
|
|
|
|
EMAIL_USE_LOCALTIME=True, USE_TZ=True, TIME_ZONE="Africa/Algiers"
|
|
|
|
|
)
|
|
|
|
|
def test_date_header_localtime(self):
|
|
|
|
|
"""
|
|
|
|
|
EMAIL_USE_LOCALTIME=True creates a datetime in the local time zone.
|
|
|
|
|
"""
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject", "Body", "bounce@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
self.assertTrue(
|
|
|
|
|
email.message()["Date"].endswith("+0100")
|
|
|
|
|
) # Africa/Algiers is UTC+1
|
|
|
|
|
|
|
|
|
|
|
2013-08-21 18:48:16 +08:00
|
|
|
|
class PythonGlobalState(SimpleTestCase):
|
2013-08-20 07:04:50 +08:00
|
|
|
|
"""
|
|
|
|
|
Tests for #12422 -- Django smarts (#2472/#11212) with charset of utf-8 text
|
|
|
|
|
parts shouldn't pollute global email Python package charset registry when
|
|
|
|
|
django.mail.message is imported.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def test_utf8(self):
|
|
|
|
|
txt = MIMEText("UTF-8 encoded body", "plain", "utf-8")
|
2014-10-28 18:02:56 +08:00
|
|
|
|
self.assertIn("Content-Transfer-Encoding: base64", txt.as_string())
|
2013-08-20 07:04:50 +08:00
|
|
|
|
|
|
|
|
|
def test_7bit(self):
|
|
|
|
|
txt = MIMEText("Body with only ASCII characters.", "plain", "utf-8")
|
2014-10-28 18:02:56 +08:00
|
|
|
|
self.assertIn("Content-Transfer-Encoding: base64", txt.as_string())
|
2013-08-20 07:04:50 +08:00
|
|
|
|
|
|
|
|
|
def test_8bit_latin(self):
|
|
|
|
|
txt = MIMEText("Body with latin characters: àáä.", "plain", "utf-8")
|
2017-01-20 17:20:53 +08:00
|
|
|
|
self.assertIn("Content-Transfer-Encoding: base64", txt.as_string())
|
2013-08-20 07:04:50 +08:00
|
|
|
|
|
|
|
|
|
def test_8bit_non_latin(self):
|
|
|
|
|
txt = MIMEText(
|
|
|
|
|
"Body with non latin characters: А Б В Г Д Е Ж Ѕ З И І К Л М Н О П.",
|
|
|
|
|
"plain",
|
|
|
|
|
"utf-8",
|
|
|
|
|
)
|
2017-01-20 17:20:53 +08:00
|
|
|
|
self.assertIn("Content-Transfer-Encoding: base64", txt.as_string())
|
2013-08-20 07:04:50 +08:00
|
|
|
|
|
|
|
|
|
|
2017-01-19 15:39:46 +08:00
|
|
|
|
class BaseEmailBackendTests(HeadersCheckMixin):
|
2011-01-15 13:55:24 +08:00
|
|
|
|
email_backend = None
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
2011-05-18 20:08:53 +08:00
|
|
|
|
self.settings_override = override_settings(EMAIL_BACKEND=self.email_backend)
|
|
|
|
|
self.settings_override.enable()
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def tearDown(self):
|
2011-05-18 20:08:53 +08:00
|
|
|
|
self.settings_override.disable()
|
2010-10-12 06:27:45 +08:00
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def assertStartsWith(self, first, second):
|
|
|
|
|
if not first.startswith(second):
|
|
|
|
|
self.longMessage = True
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
first[: len(second)],
|
|
|
|
|
second,
|
|
|
|
|
"First string doesn't start with the second.",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def get_mailbox_content(self):
|
2013-09-07 02:24:52 +08:00
|
|
|
|
raise NotImplementedError(
|
|
|
|
|
"subclasses of BaseEmailBackendTests must provide a get_mailbox_content() "
|
|
|
|
|
"method"
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def flush_mailbox(self):
|
2013-09-07 02:24:52 +08:00
|
|
|
|
raise NotImplementedError(
|
|
|
|
|
"subclasses of BaseEmailBackendTests may require a flush_mailbox() method"
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def get_the_message(self):
|
|
|
|
|
mailbox = self.get_mailbox_content()
|
2016-04-08 10:04:45 +08:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
len(mailbox),
|
|
|
|
|
1,
|
|
|
|
|
"Expected exactly one message, got %d.\n%r"
|
|
|
|
|
% (len(mailbox), [m.as_string() for m in mailbox]),
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
return mailbox[0]
|
|
|
|
|
|
|
|
|
|
def test_send(self):
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject", "Content", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
num_sent = mail.get_connection().send_messages([email])
|
|
|
|
|
self.assertEqual(num_sent, 1)
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message["subject"], "Subject")
|
|
|
|
|
self.assertEqual(message.get_payload(), "Content")
|
|
|
|
|
self.assertEqual(message["from"], "from@example.com")
|
|
|
|
|
self.assertEqual(message.get_all("to"), ["to@example.com"])
|
|
|
|
|
|
2012-11-14 17:39:58 +08:00
|
|
|
|
def test_send_unicode(self):
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Chère maman", "Je t'aime très fort", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
num_sent = mail.get_connection().send_messages([email])
|
|
|
|
|
self.assertEqual(num_sent, 1)
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message["subject"], "=?utf-8?q?Ch=C3=A8re_maman?=")
|
2018-02-08 03:20:04 +08:00
|
|
|
|
self.assertEqual(
|
|
|
|
|
message.get_payload(decode=True).decode(), "Je t'aime très fort"
|
|
|
|
|
)
|
2012-11-14 17:39:58 +08:00
|
|
|
|
|
2016-04-18 03:03:15 +08:00
|
|
|
|
def test_send_long_lines(self):
|
|
|
|
|
"""
|
|
|
|
|
Email line length is limited to 998 chars by the RFC:
|
|
|
|
|
https://tools.ietf.org/html/rfc5322#section-2.1.1
|
|
|
|
|
Message body containing longer lines are converted to Quoted-Printable
|
|
|
|
|
to avoid having to insert newlines, which could be hairy to do properly.
|
|
|
|
|
"""
|
2017-01-06 17:33:53 +08:00
|
|
|
|
# Unencoded body length is < 998 (840) but > 998 when utf-8 encoded.
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject", "В южных морях " * 60, "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
2016-04-18 03:03:15 +08:00
|
|
|
|
email.send()
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertMessageHasHeaders(
|
|
|
|
|
message,
|
|
|
|
|
{
|
|
|
|
|
("MIME-Version", "1.0"),
|
|
|
|
|
("Content-Type", 'text/plain; charset="utf-8"'),
|
|
|
|
|
("Content-Transfer-Encoding", "quoted-printable"),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_send_many(self):
|
|
|
|
|
email1 = EmailMessage(
|
|
|
|
|
"Subject", "Content1", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
email2 = EmailMessage(
|
|
|
|
|
"Subject", "Content2", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
2019-02-09 22:18:48 +08:00
|
|
|
|
# send_messages() may take a list or an iterator.
|
|
|
|
|
emails_lists = ([email1, email2], iter((email1, email2)))
|
2016-08-09 06:29:55 +08:00
|
|
|
|
for emails_list in emails_lists:
|
|
|
|
|
num_sent = mail.get_connection().send_messages(emails_list)
|
|
|
|
|
self.assertEqual(num_sent, 2)
|
|
|
|
|
messages = self.get_mailbox_content()
|
|
|
|
|
self.assertEqual(len(messages), 2)
|
|
|
|
|
self.assertEqual(messages[0].get_payload(), "Content1")
|
|
|
|
|
self.assertEqual(messages[1].get_payload(), "Content2")
|
|
|
|
|
self.flush_mailbox()
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def test_send_verbose_name(self):
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
'"Firstname Sürname" <from@example.com>',
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
)
|
|
|
|
|
email.send()
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message["subject"], "Subject")
|
|
|
|
|
self.assertEqual(message.get_payload(), "Content")
|
|
|
|
|
self.assertEqual(
|
|
|
|
|
message["from"], "=?utf-8?q?Firstname_S=C3=BCrname?= <from@example.com>"
|
|
|
|
|
)
|
|
|
|
|
|
2013-07-28 23:11:04 +08:00
|
|
|
|
def test_plaintext_send_mail(self):
|
|
|
|
|
"""
|
|
|
|
|
Test send_mail without the html_message
|
|
|
|
|
regression test for adding html_message parameter to send_mail()
|
|
|
|
|
"""
|
|
|
|
|
send_mail("Subject", "Content", "sender@example.com", ["nobody@example.com"])
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
|
|
|
|
|
self.assertEqual(message.get("subject"), "Subject")
|
|
|
|
|
self.assertEqual(message.get_all("to"), ["nobody@example.com"])
|
|
|
|
|
self.assertFalse(message.is_multipart())
|
|
|
|
|
self.assertEqual(message.get_payload(), "Content")
|
|
|
|
|
self.assertEqual(message.get_content_type(), "text/plain")
|
|
|
|
|
|
|
|
|
|
def test_html_send_mail(self):
|
|
|
|
|
"""Test html_message argument to send_mail"""
|
|
|
|
|
send_mail(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"sender@example.com",
|
|
|
|
|
["nobody@example.com"],
|
|
|
|
|
html_message="HTML Content",
|
|
|
|
|
)
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
|
|
|
|
|
self.assertEqual(message.get("subject"), "Subject")
|
|
|
|
|
self.assertEqual(message.get_all("to"), ["nobody@example.com"])
|
|
|
|
|
self.assertTrue(message.is_multipart())
|
|
|
|
|
self.assertEqual(len(message.get_payload()), 2)
|
|
|
|
|
self.assertEqual(message.get_payload(0).get_payload(), "Content")
|
|
|
|
|
self.assertEqual(message.get_payload(0).get_content_type(), "text/plain")
|
|
|
|
|
self.assertEqual(message.get_payload(1).get_payload(), "HTML Content")
|
|
|
|
|
self.assertEqual(message.get_payload(1).get_content_type(), "text/html")
|
|
|
|
|
|
2011-05-18 20:08:53 +08:00
|
|
|
|
@override_settings(MANAGERS=[("nobody", "nobody@example.com")])
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_html_mail_managers(self):
|
|
|
|
|
"""Test html_message argument to mail_managers"""
|
|
|
|
|
mail_managers("Subject", "Content", html_message="HTML Content")
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
|
|
|
|
|
self.assertEqual(message.get("subject"), "[Django] Subject")
|
|
|
|
|
self.assertEqual(message.get_all("to"), ["nobody@example.com"])
|
|
|
|
|
self.assertTrue(message.is_multipart())
|
|
|
|
|
self.assertEqual(len(message.get_payload()), 2)
|
|
|
|
|
self.assertEqual(message.get_payload(0).get_payload(), "Content")
|
|
|
|
|
self.assertEqual(message.get_payload(0).get_content_type(), "text/plain")
|
|
|
|
|
self.assertEqual(message.get_payload(1).get_payload(), "HTML Content")
|
|
|
|
|
self.assertEqual(message.get_payload(1).get_content_type(), "text/html")
|
|
|
|
|
|
2011-05-18 20:08:53 +08:00
|
|
|
|
@override_settings(ADMINS=[("nobody", "nobody@example.com")])
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_html_mail_admins(self):
|
|
|
|
|
"""Test html_message argument to mail_admins"""
|
|
|
|
|
mail_admins("Subject", "Content", html_message="HTML Content")
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
|
|
|
|
|
self.assertEqual(message.get("subject"), "[Django] Subject")
|
|
|
|
|
self.assertEqual(message.get_all("to"), ["nobody@example.com"])
|
|
|
|
|
self.assertTrue(message.is_multipart())
|
|
|
|
|
self.assertEqual(len(message.get_payload()), 2)
|
|
|
|
|
self.assertEqual(message.get_payload(0).get_payload(), "Content")
|
|
|
|
|
self.assertEqual(message.get_payload(0).get_content_type(), "text/plain")
|
|
|
|
|
self.assertEqual(message.get_payload(1).get_payload(), "HTML Content")
|
|
|
|
|
self.assertEqual(message.get_payload(1).get_content_type(), "text/html")
|
|
|
|
|
|
2011-05-18 20:08:53 +08:00
|
|
|
|
@override_settings(
|
|
|
|
|
ADMINS=[("nobody", "nobody+admin@example.com")],
|
|
|
|
|
MANAGERS=[("nobody", "nobody+manager@example.com")],
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_manager_and_admin_mail_prefix(self):
|
|
|
|
|
"""
|
|
|
|
|
String prefix + lazy translated subject = bad output
|
|
|
|
|
Regression for #13494
|
|
|
|
|
"""
|
2017-01-27 03:58:33 +08:00
|
|
|
|
mail_managers(gettext_lazy("Subject"), "Content")
|
2011-01-15 13:55:24 +08:00
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message.get("subject"), "[Django] Subject")
|
2010-10-12 06:27:45 +08:00
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
self.flush_mailbox()
|
2017-01-27 03:58:33 +08:00
|
|
|
|
mail_admins(gettext_lazy("Subject"), "Content")
|
2011-01-15 13:55:24 +08:00
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message.get("subject"), "[Django] Subject")
|
2010-10-12 06:27:45 +08:00
|
|
|
|
|
2015-01-22 00:55:57 +08:00
|
|
|
|
@override_settings(ADMINS=[], MANAGERS=[])
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_empty_admins(self):
|
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
|
mail_admins/mail_managers doesn't connect to the mail server
|
2011-01-15 13:55:24 +08:00
|
|
|
|
if there are no recipients (#9383)
|
|
|
|
|
"""
|
|
|
|
|
mail_admins("hi", "there")
|
|
|
|
|
self.assertEqual(self.get_mailbox_content(), [])
|
|
|
|
|
mail_managers("hi", "there")
|
|
|
|
|
self.assertEqual(self.get_mailbox_content(), [])
|
2010-10-15 02:37:05 +08:00
|
|
|
|
|
2019-06-29 07:50:43 +08:00
|
|
|
|
def test_wrong_admins_managers(self):
|
|
|
|
|
tests = (
|
|
|
|
|
"test@example.com",
|
|
|
|
|
("test@example.com",),
|
|
|
|
|
["test@example.com", "other@example.com"],
|
|
|
|
|
("test@example.com", "other@example.com"),
|
|
|
|
|
)
|
|
|
|
|
for setting, mail_func in (
|
|
|
|
|
("ADMINS", mail_admins),
|
|
|
|
|
("MANAGERS", mail_managers),
|
|
|
|
|
):
|
|
|
|
|
msg = "The %s setting must be a list of 2-tuples." % setting
|
|
|
|
|
for value in tests:
|
|
|
|
|
with self.subTest(setting=setting, value=value), self.settings(
|
|
|
|
|
**{setting: value}
|
|
|
|
|
):
|
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
|
mail_func("subject", "content")
|
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_message_cc_header(self):
|
|
|
|
|
"""
|
|
|
|
|
Regression test for #7722
|
|
|
|
|
"""
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
cc=["cc@example.com"],
|
|
|
|
|
)
|
|
|
|
|
mail.get_connection().send_messages([email])
|
|
|
|
|
message = self.get_the_message()
|
2013-08-20 07:04:50 +08:00
|
|
|
|
self.assertMessageHasHeaders(
|
|
|
|
|
message,
|
|
|
|
|
{
|
|
|
|
|
("MIME-Version", "1.0"),
|
|
|
|
|
("Content-Type", 'text/plain; charset="utf-8"'),
|
|
|
|
|
("Content-Transfer-Encoding", "7bit"),
|
|
|
|
|
("Subject", "Subject"),
|
|
|
|
|
("From", "from@example.com"),
|
|
|
|
|
("To", "to@example.com"),
|
|
|
|
|
("Cc", "cc@example.com"),
|
2022-02-04 03:24:19 +08:00
|
|
|
|
},
|
2013-08-20 07:04:50 +08:00
|
|
|
|
)
|
|
|
|
|
self.assertIn("\nDate: ", message.as_string())
|
2010-12-06 22:21:51 +08:00
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def test_idn_send(self):
|
|
|
|
|
"""
|
|
|
|
|
Regression test for #14301
|
|
|
|
|
"""
|
2012-06-08 00:08:47 +08:00
|
|
|
|
self.assertTrue(send_mail("Subject", "Content", "from@öäü.com", ["to@öäü.com"]))
|
2011-01-15 13:55:24 +08:00
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message.get("subject"), "Subject")
|
|
|
|
|
self.assertEqual(message.get("from"), "from@xn--4ca9at.com")
|
|
|
|
|
self.assertEqual(message.get("to"), "to@xn--4ca9at.com")
|
|
|
|
|
|
|
|
|
|
self.flush_mailbox()
|
2016-04-08 10:04:45 +08:00
|
|
|
|
m = EmailMessage(
|
|
|
|
|
"Subject", "Content", "from@öäü.com", ["to@öäü.com"], cc=["cc@öäü.com"]
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
m.send()
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message.get("subject"), "Subject")
|
|
|
|
|
self.assertEqual(message.get("from"), "from@xn--4ca9at.com")
|
|
|
|
|
self.assertEqual(message.get("to"), "to@xn--4ca9at.com")
|
|
|
|
|
self.assertEqual(message.get("cc"), "cc@xn--4ca9at.com")
|
|
|
|
|
|
|
|
|
|
def test_recipient_without_domain(self):
|
|
|
|
|
"""
|
|
|
|
|
Regression test for #15042
|
|
|
|
|
"""
|
|
|
|
|
self.assertTrue(send_mail("Subject", "Content", "tester", ["django"]))
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message.get("subject"), "Subject")
|
|
|
|
|
self.assertEqual(message.get("from"), "tester")
|
|
|
|
|
self.assertEqual(message.get("to"), "django")
|
2010-12-06 22:21:51 +08:00
|
|
|
|
|
2015-02-26 05:17:15 +08:00
|
|
|
|
def test_lazy_addresses(self):
|
|
|
|
|
"""
|
|
|
|
|
Email sending should support lazy email addresses (#24416).
|
|
|
|
|
"""
|
2017-01-27 03:58:33 +08:00
|
|
|
|
_ = gettext_lazy
|
2015-02-26 05:17:15 +08:00
|
|
|
|
self.assertTrue(send_mail("Subject", "Content", _("tester"), [_("django")]))
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message.get("from"), "tester")
|
|
|
|
|
self.assertEqual(message.get("to"), "django")
|
|
|
|
|
|
|
|
|
|
self.flush_mailbox()
|
|
|
|
|
m = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
_("tester"),
|
|
|
|
|
[_("to1"), _("to2")],
|
|
|
|
|
cc=[_("cc1"), _("cc2")],
|
|
|
|
|
bcc=[_("bcc")],
|
|
|
|
|
reply_to=[_("reply")],
|
|
|
|
|
)
|
|
|
|
|
self.assertEqual(m.recipients(), ["to1", "to2", "cc1", "cc2", "bcc"])
|
|
|
|
|
m.send()
|
|
|
|
|
message = self.get_the_message()
|
|
|
|
|
self.assertEqual(message.get("from"), "tester")
|
|
|
|
|
self.assertEqual(message.get("to"), "to1, to2")
|
|
|
|
|
self.assertEqual(message.get("cc"), "cc1, cc2")
|
|
|
|
|
self.assertEqual(message.get("Reply-To"), "reply")
|
|
|
|
|
|
2013-01-04 03:41:45 +08:00
|
|
|
|
def test_close_connection(self):
|
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
|
Connection can be closed (even when not explicitly opened)
|
2013-01-04 03:41:45 +08:00
|
|
|
|
"""
|
|
|
|
|
conn = mail.get_connection(username="", password="")
|
2015-06-05 15:32:29 +08:00
|
|
|
|
conn.close()
|
2013-01-04 03:41:45 +08:00
|
|
|
|
|
2014-03-24 00:29:10 +08:00
|
|
|
|
def test_use_as_contextmanager(self):
|
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
|
The connection can be used as a contextmanager.
|
2014-03-24 00:29:10 +08:00
|
|
|
|
"""
|
|
|
|
|
opened = [False]
|
|
|
|
|
closed = [False]
|
|
|
|
|
conn = mail.get_connection(username="", password="")
|
|
|
|
|
|
|
|
|
|
def open():
|
|
|
|
|
opened[0] = True
|
2022-02-04 03:24:19 +08:00
|
|
|
|
|
2014-03-24 00:29:10 +08:00
|
|
|
|
conn.open = open
|
|
|
|
|
|
|
|
|
|
def close():
|
|
|
|
|
closed[0] = True
|
2022-02-04 03:24:19 +08:00
|
|
|
|
|
2014-03-24 00:29:10 +08:00
|
|
|
|
conn.close = close
|
|
|
|
|
with conn as same_conn:
|
|
|
|
|
self.assertTrue(opened[0])
|
|
|
|
|
self.assertIs(same_conn, conn)
|
|
|
|
|
self.assertFalse(closed[0])
|
|
|
|
|
self.assertTrue(closed[0])
|
|
|
|
|
|
2010-12-06 22:21:51 +08:00
|
|
|
|
|
2013-08-21 18:48:16 +08:00
|
|
|
|
class LocmemBackendTests(BaseEmailBackendTests, SimpleTestCase):
|
2011-01-15 13:55:24 +08:00
|
|
|
|
email_backend = "django.core.mail.backends.locmem.EmailBackend"
|
2010-12-06 22:21:51 +08:00
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def get_mailbox_content(self):
|
|
|
|
|
return [m.message() for m in mail.outbox]
|
2010-12-06 22:21:51 +08:00
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def flush_mailbox(self):
|
|
|
|
|
mail.outbox = []
|
2010-12-06 22:21:51 +08:00
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def tearDown(self):
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().tearDown()
|
2010-10-15 02:37:05 +08:00
|
|
|
|
mail.outbox = []
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def test_locmem_shared_messages(self):
|
|
|
|
|
"""
|
|
|
|
|
Make sure that the locmen backend populates the outbox.
|
|
|
|
|
"""
|
|
|
|
|
connection = locmem.EmailBackend()
|
|
|
|
|
connection2 = locmem.EmailBackend()
|
2015-09-12 07:33:12 +08:00
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
connection.send_messages([email])
|
|
|
|
|
connection2.send_messages([email])
|
|
|
|
|
self.assertEqual(len(mail.outbox), 2)
|
|
|
|
|
|
2012-09-22 21:17:13 +08:00
|
|
|
|
def test_validate_multiline_headers(self):
|
|
|
|
|
# Ticket #18861 - Validate emails when using the locmem backend
|
|
|
|
|
with self.assertRaises(BadHeaderError):
|
|
|
|
|
send_mail(
|
|
|
|
|
"Subject\nMultiline", "Content", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
2013-08-21 18:48:16 +08:00
|
|
|
|
class FileBackendTests(BaseEmailBackendTests, SimpleTestCase):
|
2011-01-15 13:55:24 +08:00
|
|
|
|
email_backend = "django.core.mail.backends.filebased.EmailBackend"
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().setUp()
|
2019-11-06 16:33:07 +08:00
|
|
|
|
self.tmp_dir = self.mkdtemp()
|
2011-05-18 20:08:53 +08:00
|
|
|
|
self.addCleanup(shutil.rmtree, self.tmp_dir)
|
2012-06-03 01:44:06 +08:00
|
|
|
|
self._settings_override = override_settings(EMAIL_FILE_PATH=self.tmp_dir)
|
|
|
|
|
self._settings_override.enable()
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def tearDown(self):
|
2012-06-03 01:44:06 +08:00
|
|
|
|
self._settings_override.disable()
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().tearDown()
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
2019-11-06 16:33:07 +08:00
|
|
|
|
def mkdtemp(self):
|
|
|
|
|
return tempfile.mkdtemp()
|
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
def flush_mailbox(self):
|
|
|
|
|
for filename in os.listdir(self.tmp_dir):
|
|
|
|
|
os.unlink(os.path.join(self.tmp_dir, filename))
|
|
|
|
|
|
|
|
|
|
def get_mailbox_content(self):
|
|
|
|
|
messages = []
|
|
|
|
|
for filename in os.listdir(self.tmp_dir):
|
2013-12-31 06:45:43 +08:00
|
|
|
|
with open(os.path.join(self.tmp_dir, filename), "rb") as fp:
|
2018-02-08 03:20:04 +08:00
|
|
|
|
session = fp.read().split(b"\n" + (b"-" * 79) + b"\n")
|
2013-12-31 06:45:43 +08:00
|
|
|
|
messages.extend(message_from_bytes(m) for m in session if m)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
return messages
|
|
|
|
|
|
|
|
|
|
def test_file_sessions(self):
|
|
|
|
|
"""Make sure opening a connection creates a new file"""
|
2015-09-12 07:33:12 +08:00
|
|
|
|
msg = EmailMessage(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"bounce@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
headers={"From": "from@example.com"},
|
|
|
|
|
)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
connection = mail.get_connection()
|
|
|
|
|
connection.send_messages([msg])
|
|
|
|
|
|
|
|
|
|
self.assertEqual(len(os.listdir(self.tmp_dir)), 1)
|
2013-12-31 06:45:43 +08:00
|
|
|
|
with open(os.path.join(self.tmp_dir, os.listdir(self.tmp_dir)[0]), "rb") as fp:
|
|
|
|
|
message = message_from_binary_file(fp)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
self.assertEqual(message.get_content_type(), "text/plain")
|
|
|
|
|
self.assertEqual(message.get("subject"), "Subject")
|
|
|
|
|
self.assertEqual(message.get("from"), "from@example.com")
|
|
|
|
|
self.assertEqual(message.get("to"), "to@example.com")
|
|
|
|
|
|
|
|
|
|
connection2 = mail.get_connection()
|
|
|
|
|
connection2.send_messages([msg])
|
|
|
|
|
self.assertEqual(len(os.listdir(self.tmp_dir)), 2)
|
|
|
|
|
|
|
|
|
|
connection.send_messages([msg])
|
|
|
|
|
self.assertEqual(len(os.listdir(self.tmp_dir)), 2)
|
|
|
|
|
|
|
|
|
|
msg.connection = mail.get_connection()
|
|
|
|
|
self.assertTrue(connection.open())
|
|
|
|
|
msg.send()
|
|
|
|
|
self.assertEqual(len(os.listdir(self.tmp_dir)), 3)
|
|
|
|
|
msg.send()
|
|
|
|
|
self.assertEqual(len(os.listdir(self.tmp_dir)), 3)
|
|
|
|
|
|
2012-08-15 17:16:28 +08:00
|
|
|
|
connection.close()
|
|
|
|
|
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
2019-11-06 16:33:07 +08:00
|
|
|
|
class FileBackendPathLibTests(FileBackendTests):
|
|
|
|
|
def mkdtemp(self):
|
|
|
|
|
tmp_dir = super().mkdtemp()
|
|
|
|
|
return Path(tmp_dir)
|
|
|
|
|
|
|
|
|
|
|
2013-08-21 18:48:16 +08:00
|
|
|
|
class ConsoleBackendTests(BaseEmailBackendTests, SimpleTestCase):
|
2011-01-15 13:55:24 +08:00
|
|
|
|
email_backend = "django.core.mail.backends.console.EmailBackend"
|
|
|
|
|
|
|
|
|
|
def setUp(self):
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().setUp()
|
2011-01-15 13:55:24 +08:00
|
|
|
|
self.__stdout = sys.stdout
|
|
|
|
|
self.stream = sys.stdout = StringIO()
|
|
|
|
|
|
|
|
|
|
def tearDown(self):
|
|
|
|
|
del self.stream
|
|
|
|
|
sys.stdout = self.__stdout
|
|
|
|
|
del self.__stdout
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().tearDown()
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def flush_mailbox(self):
|
|
|
|
|
self.stream = sys.stdout = StringIO()
|
|
|
|
|
|
|
|
|
|
def get_mailbox_content(self):
|
2017-01-20 17:20:53 +08:00
|
|
|
|
messages = self.stream.getvalue().split("\n" + ("-" * 79) + "\n")
|
2018-08-04 22:15:51 +08:00
|
|
|
|
return [message_from_bytes(m.encode()) for m in messages if m]
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def test_console_stream_kwarg(self):
|
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
|
The console backend can be pointed at an arbitrary stream.
|
2011-01-15 13:55:24 +08:00
|
|
|
|
"""
|
|
|
|
|
s = StringIO()
|
|
|
|
|
connection = mail.get_connection(
|
|
|
|
|
"django.core.mail.backends.console.EmailBackend", stream=s
|
|
|
|
|
)
|
|
|
|
|
send_mail(
|
|
|
|
|
"Subject",
|
|
|
|
|
"Content",
|
|
|
|
|
"from@example.com",
|
|
|
|
|
["to@example.com"],
|
|
|
|
|
connection=connection,
|
2022-02-04 03:24:19 +08:00
|
|
|
|
)
|
2018-08-04 22:15:51 +08:00
|
|
|
|
message = s.getvalue().split("\n" + ("-" * 79) + "\n")[0].encode()
|
2013-12-31 06:45:43 +08:00
|
|
|
|
self.assertMessageHasHeaders(
|
|
|
|
|
message,
|
|
|
|
|
{
|
2013-08-20 07:04:50 +08:00
|
|
|
|
("MIME-Version", "1.0"),
|
|
|
|
|
("Content-Type", 'text/plain; charset="utf-8"'),
|
|
|
|
|
("Content-Transfer-Encoding", "7bit"),
|
|
|
|
|
("Subject", "Subject"),
|
|
|
|
|
("From", "from@example.com"),
|
|
|
|
|
("To", "to@example.com"),
|
2022-02-04 03:24:19 +08:00
|
|
|
|
},
|
2013-08-20 07:04:50 +08:00
|
|
|
|
)
|
2013-12-31 06:45:43 +08:00
|
|
|
|
self.assertIn(b"\nDate: ", message)
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
class SMTPHandler:
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
self.mailbox = []
|
2013-07-12 02:58:06 +08:00
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
async def handle_DATA(self, server, session, envelope):
|
|
|
|
|
data = envelope.content
|
|
|
|
|
mail_from = envelope.mail_from
|
2013-07-12 02:58:06 +08:00
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
message = message_from_bytes(data.rstrip())
|
|
|
|
|
message_addr = parseaddr(message.get("from"))[1]
|
|
|
|
|
if mail_from != message_addr:
|
|
|
|
|
# According to the spec, mail_from does not necessarily match the
|
2017-01-26 02:59:25 +08:00
|
|
|
|
# From header - this is the case where the local part isn't
|
|
|
|
|
# encoded, so try to correct that.
|
2021-10-15 15:58:35 +08:00
|
|
|
|
lp, domain = mail_from.split("@", 1)
|
2016-04-02 17:41:47 +08:00
|
|
|
|
lp = Header(lp, "utf-8").encode()
|
2021-10-15 15:58:35 +08:00
|
|
|
|
mail_from = "@".join([lp, domain])
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
if mail_from != message_addr:
|
|
|
|
|
return f"553 '{mail_from}' != '{message_addr}'"
|
|
|
|
|
self.mailbox.append(message)
|
|
|
|
|
return "250 OK"
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
def flush_mailbox(self):
|
|
|
|
|
self.mailbox[:] = []
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
@skipUnless(HAS_AIOSMTPD, "No aiosmtpd library detected.")
|
2014-12-06 00:15:33 +08:00
|
|
|
|
class SMTPBackendTestsBase(SimpleTestCase):
|
2011-01-15 13:55:24 +08:00
|
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls):
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().setUpClass()
|
2021-10-15 15:58:35 +08:00
|
|
|
|
# Find a free port.
|
|
|
|
|
with socket.socket() as s:
|
|
|
|
|
s.bind(("127.0.0.1", 0))
|
|
|
|
|
port = s.getsockname()[1]
|
|
|
|
|
cls.smtp_handler = SMTPHandler()
|
|
|
|
|
cls.smtp_controller = Controller(
|
|
|
|
|
cls.smtp_handler,
|
|
|
|
|
hostname="127.0.0.1",
|
|
|
|
|
port=port,
|
|
|
|
|
)
|
2012-06-03 01:44:06 +08:00
|
|
|
|
cls._settings_override = override_settings(
|
2021-10-15 15:58:35 +08:00
|
|
|
|
EMAIL_HOST=cls.smtp_controller.hostname,
|
|
|
|
|
EMAIL_PORT=cls.smtp_controller.port,
|
|
|
|
|
)
|
2012-06-03 01:44:06 +08:00
|
|
|
|
cls._settings_override.enable()
|
2021-06-04 18:53:11 +08:00
|
|
|
|
cls.addClassCleanup(cls._settings_override.disable)
|
2021-10-15 15:58:35 +08:00
|
|
|
|
cls.smtp_controller.start()
|
|
|
|
|
cls.addClassCleanup(cls.stop_smtp)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def stop_smtp(cls):
|
|
|
|
|
cls.smtp_controller.stop()
|
2014-12-06 00:15:33 +08:00
|
|
|
|
|
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
@skipUnless(HAS_AIOSMTPD, "No aiosmtpd library detected.")
|
2014-12-06 00:15:33 +08:00
|
|
|
|
class SMTPBackendTests(BaseEmailBackendTests, SMTPBackendTestsBase):
|
|
|
|
|
email_backend = "django.core.mail.backends.smtp.EmailBackend"
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def setUp(self):
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().setUp()
|
2021-10-15 15:58:35 +08:00
|
|
|
|
self.smtp_handler.flush_mailbox()
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def tearDown(self):
|
2021-10-15 15:58:35 +08:00
|
|
|
|
self.smtp_handler.flush_mailbox()
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().tearDown()
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def flush_mailbox(self):
|
2021-10-15 15:58:35 +08:00
|
|
|
|
self.smtp_handler.flush_mailbox()
|
2011-01-15 13:55:24 +08:00
|
|
|
|
|
|
|
|
|
def get_mailbox_content(self):
|
2021-10-15 15:58:35 +08:00
|
|
|
|
return self.smtp_handler.mailbox
|
2011-07-04 01:56:05 +08:00
|
|
|
|
|
2013-12-13 04:23:24 +08:00
|
|
|
|
@override_settings(
|
|
|
|
|
EMAIL_HOST_USER="not empty username",
|
2018-03-16 17:54:34 +08:00
|
|
|
|
EMAIL_HOST_PASSWORD="not empty password",
|
|
|
|
|
)
|
2011-07-04 01:56:05 +08:00
|
|
|
|
def test_email_authentication_use_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertEqual(backend.username, "not empty username")
|
|
|
|
|
self.assertEqual(backend.password, "not empty password")
|
|
|
|
|
|
2013-12-13 04:23:24 +08:00
|
|
|
|
@override_settings(
|
|
|
|
|
EMAIL_HOST_USER="not empty username",
|
2018-03-16 17:54:34 +08:00
|
|
|
|
EMAIL_HOST_PASSWORD="not empty password",
|
|
|
|
|
)
|
2011-07-04 01:56:05 +08:00
|
|
|
|
def test_email_authentication_override_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend(username="username", password="password")
|
|
|
|
|
self.assertEqual(backend.username, "username")
|
|
|
|
|
self.assertEqual(backend.password, "password")
|
|
|
|
|
|
2013-12-13 04:23:24 +08:00
|
|
|
|
@override_settings(
|
|
|
|
|
EMAIL_HOST_USER="not empty username",
|
2018-03-16 17:54:34 +08:00
|
|
|
|
EMAIL_HOST_PASSWORD="not empty password",
|
|
|
|
|
)
|
2011-07-04 01:56:05 +08:00
|
|
|
|
def test_email_disabled_authentication(self):
|
|
|
|
|
backend = smtp.EmailBackend(username="", password="")
|
|
|
|
|
self.assertEqual(backend.username, "")
|
|
|
|
|
self.assertEqual(backend.password, "")
|
2013-01-04 01:49:00 +08:00
|
|
|
|
|
2013-10-25 17:19:41 +08:00
|
|
|
|
def test_auth_attempted(self):
|
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
|
Opening the backend with non empty username/password tries
|
2013-10-25 17:19:41 +08:00
|
|
|
|
to authenticate against the SMTP server.
|
|
|
|
|
"""
|
|
|
|
|
backend = smtp.EmailBackend(
|
|
|
|
|
username="not empty username", password="not empty password"
|
|
|
|
|
)
|
2016-10-21 20:59:07 +08:00
|
|
|
|
with self.assertRaisesMessage(
|
|
|
|
|
SMTPException, "SMTP AUTH extension not supported by server."
|
|
|
|
|
):
|
|
|
|
|
with backend:
|
|
|
|
|
pass
|
2013-10-25 17:19:41 +08:00
|
|
|
|
|
2014-01-13 07:33:48 +08:00
|
|
|
|
def test_server_open(self):
|
|
|
|
|
"""
|
2016-10-27 15:53:39 +08:00
|
|
|
|
open() returns whether it opened a connection.
|
2014-01-13 07:33:48 +08:00
|
|
|
|
"""
|
|
|
|
|
backend = smtp.EmailBackend(username="", password="")
|
2018-01-15 21:53:42 +08:00
|
|
|
|
self.assertIsNone(backend.connection)
|
2014-01-13 07:33:48 +08:00
|
|
|
|
opened = backend.open()
|
|
|
|
|
backend.close()
|
2018-01-15 21:53:42 +08:00
|
|
|
|
self.assertIs(opened, True)
|
|
|
|
|
|
|
|
|
|
def test_reopen_connection(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
# Simulate an already open connection.
|
2019-12-01 23:01:51 +08:00
|
|
|
|
backend.connection = mock.Mock(spec=object())
|
2018-01-15 21:53:42 +08:00
|
|
|
|
self.assertIs(backend.open(), False)
|
2014-01-13 07:33:48 +08:00
|
|
|
|
|
2013-07-12 02:58:06 +08:00
|
|
|
|
@override_settings(EMAIL_USE_TLS=True)
|
|
|
|
|
def test_email_tls_use_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertTrue(backend.use_tls)
|
|
|
|
|
|
|
|
|
|
@override_settings(EMAIL_USE_TLS=True)
|
|
|
|
|
def test_email_tls_override_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend(use_tls=False)
|
|
|
|
|
self.assertFalse(backend.use_tls)
|
|
|
|
|
|
|
|
|
|
def test_email_tls_default_disabled(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertFalse(backend.use_tls)
|
|
|
|
|
|
2018-01-15 21:53:42 +08:00
|
|
|
|
def test_ssl_tls_mutually_exclusive(self):
|
|
|
|
|
msg = (
|
|
|
|
|
"EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
|
|
|
|
|
"one of those settings to True."
|
|
|
|
|
)
|
|
|
|
|
with self.assertRaisesMessage(ValueError, msg):
|
|
|
|
|
smtp.EmailBackend(use_ssl=True, use_tls=True)
|
|
|
|
|
|
2013-07-12 02:58:06 +08:00
|
|
|
|
@override_settings(EMAIL_USE_SSL=True)
|
|
|
|
|
def test_email_ssl_use_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertTrue(backend.use_ssl)
|
|
|
|
|
|
|
|
|
|
@override_settings(EMAIL_USE_SSL=True)
|
|
|
|
|
def test_email_ssl_override_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend(use_ssl=False)
|
|
|
|
|
self.assertFalse(backend.use_ssl)
|
|
|
|
|
|
|
|
|
|
def test_email_ssl_default_disabled(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertFalse(backend.use_ssl)
|
|
|
|
|
|
2014-05-18 15:56:55 +08:00
|
|
|
|
@override_settings(EMAIL_SSL_CERTFILE="foo")
|
|
|
|
|
def test_email_ssl_certfile_use_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertEqual(backend.ssl_certfile, "foo")
|
|
|
|
|
|
|
|
|
|
@override_settings(EMAIL_SSL_CERTFILE="foo")
|
|
|
|
|
def test_email_ssl_certfile_override_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend(ssl_certfile="bar")
|
|
|
|
|
self.assertEqual(backend.ssl_certfile, "bar")
|
|
|
|
|
|
|
|
|
|
def test_email_ssl_certfile_default_disabled(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
2016-06-17 02:19:18 +08:00
|
|
|
|
self.assertIsNone(backend.ssl_certfile)
|
2014-05-18 15:56:55 +08:00
|
|
|
|
|
|
|
|
|
@override_settings(EMAIL_SSL_KEYFILE="foo")
|
|
|
|
|
def test_email_ssl_keyfile_use_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertEqual(backend.ssl_keyfile, "foo")
|
|
|
|
|
|
|
|
|
|
@override_settings(EMAIL_SSL_KEYFILE="foo")
|
|
|
|
|
def test_email_ssl_keyfile_override_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend(ssl_keyfile="bar")
|
|
|
|
|
self.assertEqual(backend.ssl_keyfile, "bar")
|
|
|
|
|
|
|
|
|
|
def test_email_ssl_keyfile_default_disabled(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
2016-06-17 02:19:18 +08:00
|
|
|
|
self.assertIsNone(backend.ssl_keyfile)
|
2014-05-18 15:56:55 +08:00
|
|
|
|
|
2013-07-12 02:58:06 +08:00
|
|
|
|
@override_settings(EMAIL_USE_TLS=True)
|
|
|
|
|
def test_email_tls_attempts_starttls(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertTrue(backend.use_tls)
|
2016-10-21 20:59:07 +08:00
|
|
|
|
with self.assertRaisesMessage(
|
|
|
|
|
SMTPException, "STARTTLS extension not supported by server."
|
|
|
|
|
):
|
|
|
|
|
with backend:
|
|
|
|
|
pass
|
2013-07-12 02:58:06 +08:00
|
|
|
|
|
|
|
|
|
@override_settings(EMAIL_USE_SSL=True)
|
|
|
|
|
def test_email_ssl_attempts_ssl_connection(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertTrue(backend.use_ssl)
|
2016-10-21 20:59:07 +08:00
|
|
|
|
with self.assertRaises(SSLError):
|
|
|
|
|
with backend:
|
|
|
|
|
pass
|
2013-10-15 23:18:06 +08:00
|
|
|
|
|
|
|
|
|
def test_connection_timeout_default(self):
|
2016-10-27 15:53:39 +08:00
|
|
|
|
"""The connection's timeout value is None by default."""
|
2013-10-15 23:18:06 +08:00
|
|
|
|
connection = mail.get_connection("django.core.mail.backends.smtp.EmailBackend")
|
2016-06-17 02:19:18 +08:00
|
|
|
|
self.assertIsNone(connection.timeout)
|
2013-10-15 23:18:06 +08:00
|
|
|
|
|
|
|
|
|
def test_connection_timeout_custom(self):
|
2016-10-27 15:53:39 +08:00
|
|
|
|
"""The timeout parameter can be customized."""
|
2022-02-04 03:24:19 +08:00
|
|
|
|
|
2013-10-15 23:18:06 +08:00
|
|
|
|
class MyEmailBackend(smtp.EmailBackend):
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
|
kwargs.setdefault("timeout", 42)
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().__init__(*args, **kwargs)
|
2013-10-15 23:18:06 +08:00
|
|
|
|
|
|
|
|
|
myemailbackend = MyEmailBackend()
|
|
|
|
|
myemailbackend.open()
|
|
|
|
|
self.assertEqual(myemailbackend.timeout, 42)
|
|
|
|
|
self.assertEqual(myemailbackend.connection.timeout, 42)
|
2014-01-13 07:39:44 +08:00
|
|
|
|
myemailbackend.close()
|
2014-09-13 10:46:22 +08:00
|
|
|
|
|
|
|
|
|
@override_settings(EMAIL_TIMEOUT=10)
|
|
|
|
|
def test_email_timeout_override_settings(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
self.assertEqual(backend.timeout, 10)
|
2014-10-09 20:18:31 +08:00
|
|
|
|
|
|
|
|
|
def test_email_msg_uses_crlf(self):
|
2016-10-27 15:53:39 +08:00
|
|
|
|
"""#23063 -- RFC-compliant messages are sent over SMTP."""
|
2014-10-09 20:18:31 +08:00
|
|
|
|
send = SMTP.send
|
|
|
|
|
try:
|
|
|
|
|
smtp_messages = []
|
|
|
|
|
|
|
|
|
|
def mock_send(self, s):
|
|
|
|
|
smtp_messages.append(s)
|
|
|
|
|
return send(self, s)
|
|
|
|
|
|
|
|
|
|
SMTP.send = mock_send
|
|
|
|
|
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject", "Content", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
|
|
|
|
mail.get_connection().send_messages([email])
|
|
|
|
|
|
|
|
|
|
# Find the actual message
|
|
|
|
|
msg = None
|
|
|
|
|
for i, m in enumerate(smtp_messages):
|
|
|
|
|
if m[:4] == "data":
|
2014-10-16 04:35:54 +08:00
|
|
|
|
msg = smtp_messages[i + 1]
|
2014-10-09 20:18:31 +08:00
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
self.assertTrue(msg)
|
|
|
|
|
|
2017-02-08 01:05:47 +08:00
|
|
|
|
msg = msg.decode()
|
2016-10-27 15:53:39 +08:00
|
|
|
|
# The message only contains CRLF and not combinations of CRLF, LF, and CR.
|
2014-10-09 20:18:31 +08:00
|
|
|
|
msg = msg.replace("\r\n", "")
|
|
|
|
|
self.assertNotIn("\r", msg)
|
|
|
|
|
self.assertNotIn("\n", msg)
|
|
|
|
|
|
|
|
|
|
finally:
|
|
|
|
|
SMTP.send = send
|
2014-12-06 00:15:33 +08:00
|
|
|
|
|
2016-09-21 01:34:01 +08:00
|
|
|
|
def test_send_messages_after_open_failed(self):
|
|
|
|
|
"""
|
|
|
|
|
send_messages() shouldn't try to send messages if open() raises an
|
|
|
|
|
exception after initializing the connection.
|
|
|
|
|
"""
|
|
|
|
|
backend = smtp.EmailBackend()
|
|
|
|
|
# Simulate connection initialization success and a subsequent
|
|
|
|
|
# connection exception.
|
2019-12-01 23:01:51 +08:00
|
|
|
|
backend.connection = mock.Mock(spec=object())
|
2016-09-21 01:34:01 +08:00
|
|
|
|
backend.open = lambda: None
|
|
|
|
|
email = EmailMessage(
|
|
|
|
|
"Subject", "Content", "from@example.com", ["to@example.com"]
|
|
|
|
|
)
|
2018-12-23 09:06:56 +08:00
|
|
|
|
self.assertEqual(backend.send_messages([email]), 0)
|
|
|
|
|
|
|
|
|
|
def test_send_messages_empty_list(self):
|
|
|
|
|
backend = smtp.EmailBackend()
|
2019-12-01 23:01:51 +08:00
|
|
|
|
backend.connection = mock.Mock(spec=object())
|
2018-12-23 09:06:56 +08:00
|
|
|
|
self.assertEqual(backend.send_messages([]), 0)
|
2016-09-21 01:34:01 +08:00
|
|
|
|
|
2018-01-15 21:53:42 +08:00
|
|
|
|
def test_send_messages_zero_sent(self):
|
|
|
|
|
"""A message isn't sent if it doesn't have any recipients."""
|
|
|
|
|
backend = smtp.EmailBackend()
|
2019-12-01 23:01:51 +08:00
|
|
|
|
backend.connection = mock.Mock(spec=object())
|
2018-01-15 21:53:42 +08:00
|
|
|
|
email = EmailMessage("Subject", "Content", "from@example.com", to=[])
|
|
|
|
|
sent = backend.send_messages([email])
|
|
|
|
|
self.assertEqual(sent, 0)
|
|
|
|
|
|
2016-09-28 02:34:49 +08:00
|
|
|
|
|
2021-10-15 15:58:35 +08:00
|
|
|
|
@skipUnless(HAS_AIOSMTPD, "No aiosmtpd library detected.")
|
2016-10-01 00:22:52 +08:00
|
|
|
|
class SMTPBackendStoppedServerTests(SMTPBackendTestsBase):
|
|
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls):
|
2017-01-21 21:13:44 +08:00
|
|
|
|
super().setUpClass()
|
2016-10-01 00:22:52 +08:00
|
|
|
|
cls.backend = smtp.EmailBackend(username="", password="")
|
2021-10-15 15:58:35 +08:00
|
|
|
|
cls.smtp_controller.stop()
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def stop_smtp(cls):
|
|
|
|
|
# SMTP controller is stopped in setUpClass().
|
|
|
|
|
pass
|
2014-12-06 00:15:33 +08:00
|
|
|
|
|
|
|
|
|
def test_server_stopped(self):
|
|
|
|
|
"""
|
2016-10-01 00:22:52 +08:00
|
|
|
|
Closing the backend while the SMTP server is stopped doesn't raise an
|
|
|
|
|
exception.
|
2014-12-06 00:15:33 +08:00
|
|
|
|
"""
|
2016-10-01 00:22:52 +08:00
|
|
|
|
self.backend.close()
|
|
|
|
|
|
|
|
|
|
def test_fail_silently_on_connection_error(self):
|
|
|
|
|
"""
|
|
|
|
|
A socket connection error is silenced with fail_silently=True.
|
|
|
|
|
"""
|
2019-01-28 23:01:35 +08:00
|
|
|
|
with self.assertRaises(ConnectionError):
|
2016-10-01 00:22:52 +08:00
|
|
|
|
self.backend.open()
|
|
|
|
|
self.backend.fail_silently = True
|
|
|
|
|
self.backend.open()
|