Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #35033 -- Avoid repeated header in email message #17642

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions django/core/mail/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ def __init__(
else:
self.attach(*attachment)
self.extra_headers = headers or {}
self.headers_with_one_occurrence = {
"To": self.to,
"Cc": self.cc,
"Reply-To": self.reply_to,
}
self.connection = connection

def get_connection(self, fail_silently=False):
Expand All @@ -258,6 +263,8 @@ def get_connection(self, fail_silently=False):
return self.connection

def message(self):
HEADER_WITH_ONE_OCCURRENCE = ["to", "reply-to", "cc"]

felixxm marked this conversation as resolved.
Show resolved Hide resolved
encoding = self.encoding or settings.DEFAULT_CHARSET
msg = SafeMIMEText(self.body, self.content_subtype, encoding)
msg = self._create_message(msg)
Expand All @@ -280,8 +287,14 @@ def message(self):
# Use cached DNS_NAME for performance
msg["Message-ID"] = make_msgid(domain=DNS_NAME)
for name, value in self.extra_headers.items():
if name.lower() in HEADER_WITH_ONE_OCCURRENCE and msg[name] is not None:
felixxm marked this conversation as resolved.
Show resolved Hide resolved
raise ValueError("Cannot insert repeated headers")
if name.lower() != "from": # From is already handled
msg[name] = value
felixxm marked this conversation as resolved.
Show resolved Hide resolved
for header, value_header in self.headers_with_one_occurrence.items():
# extra_headers takes precedence over to/cc/bcc/reply_to parameters.
if msg.get(header) is None and value_header:
msg[header] = ", ".join(str(v) for v in value_header)
return msg

def recipients(self):
Expand Down
39 changes: 31 additions & 8 deletions tests/mail/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,28 @@ def test_recipients_with_empty_strings(self):
email.recipients(), ["to@example.com", "cc@example.com", "bcc@example.com"]
)

def test_headers_not_repeated(self):
tests = ["To", "Cc", "Reply_To"]
for header in tests:
with self.subTest(header=header):
header_attr = header.lower()
header = header.replace("_", "-")
email = EmailMessage(
"Subject",
"Content",
from_email="bounce@example.com",
**{
header_attr: ["test@example.com"],
"headers": {header: "precedence@example.com"},
},
)
message = email.message()
self.assertEqual(
message.get_all(header),
["precedence@example.com"],
)
self.assertEqual(getattr(email, header_attr), ["test@example.com"])

def test_cc(self):
"""Regression test for #7722"""
email = EmailMessage(
Expand Down Expand Up @@ -191,9 +213,8 @@ def test_cc_headers(self):
"bounce@example.com",
["to@example.com"],
cc=["foo@example.com"],
headers={"Cc": "override@example.com"},
).message()
self.assertEqual(message["Cc"], "override@example.com")
self.assertEqual(message["Cc"], "foo@example.com")

def test_cc_in_headers_only(self):
message = EmailMessage(
Expand Down Expand Up @@ -359,14 +380,17 @@ def test_to_header(self):
"Subject",
"Content",
"bounce@example.com",
["list-subscriber@example.com", "list-subscriber2@example.com"],
headers={"To": "mailing-list@example.com"},
felixxm marked this conversation as resolved.
Show resolved Hide resolved
headers={
"To": ", ".join(
["guy1@example.com", "guy2@example.com", "guy3@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"]
message["To"], "guy1@example.com, guy2@example.com, guy3@example.com"
)
self.assertEqual(email.to, [])

# If we don't set the To header manually, it should default to the `to`
# argument to the constructor.
Expand Down Expand Up @@ -403,10 +427,9 @@ def test_reply_to_header(self):
"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")
self.assertEqual(message["Reply-To"], "foo@example.com")

def test_reply_to_in_headers_only(self):
message = EmailMessage(
Expand Down