Permalink
Browse files

[1.2.X] Fixed #15042 -- Ensured that email addresses without a domain…

… can still be mail recipients. Patch also improves the IDN handling introduced by r15006, and refactors the test suite to ensure even feature coverage. Thanks to net147 for the report, and to Łukasz Rekucki for the awesome patch.

Backport of r15211 from trunk.

git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.2.X@15213 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent f7d04c3 commit 84db894f44c8bf551a14bcdf790234d71fd62d34 @freakboy3742 freakboy3742 committed Jan 15, 2011
Showing with 432 additions and 216 deletions.
  1. +5 −8 django/core/mail/backends/smtp.py
  2. +48 −17 django/core/mail/message.py
  3. +379 −191 tests/regressiontests/mail/tests.py
@@ -1,12 +1,13 @@
"""SMTP email backend class."""
-
import smtplib
import socket
import threading
from django.conf import settings
from django.core.mail.backends.base import BaseEmailBackend
from django.core.mail.utils import DNS_NAME
+from django.core.mail.message import sanitize_address
+
class EmailBackend(BaseEmailBackend):
"""
@@ -91,17 +92,13 @@ def send_messages(self, email_messages):
self._lock.release()
return num_sent
- def _sanitize(self, email):
- name, domain = email.split('@', 1)
- email = '@'.join([name, domain.encode('idna')])
- return email
-
def _send(self, email_message):
"""A helper method that does the actual sending."""
if not email_message.recipients():
return False
- from_email = self._sanitize(email_message.from_email)
- recipients = map(self._sanitize, email_message.recipients())
+ from_email = sanitize_address(email_message.from_email, email_message.encoding)
+ recipients = [sanitize_address(addr, email_message.encoding)
+ for addr in email_message.recipients()]
try:
self.connection.sendmail(from_email, recipients,
email_message.message().as_string())
@@ -12,6 +12,7 @@
from django.conf import settings
from django.core.mail.utils import DNS_NAME
from django.utils.encoding import smart_str, force_unicode
+from email.Utils import parseaddr
# Don't BASE64-encode UTF-8 messages so that we avoid unwanted attention from
# some spam filters.
@@ -54,6 +55,22 @@ def make_msgid(idstring=None):
return msgid
+# Header names that contain structured address data (RFC #5322)
+ADDRESS_HEADERS = set([
+ 'from',
+ 'sender',
+ 'reply-to',
+ 'to',
+ 'cc',
+ 'bcc',
+ 'resent-from',
+ 'resent-sender',
+ 'resent-to',
+ 'resent-cc',
+ 'resent-bcc',
+])
+
+
def forbid_multi_line_headers(name, val, encoding):
"""Forbids multi-line headers, to prevent header injection."""
encoding = encoding or settings.DEFAULT_CHARSET
@@ -63,43 +80,57 @@ def forbid_multi_line_headers(name, val, encoding):
try:
val = val.encode('ascii')
except UnicodeEncodeError:
- if name.lower() in ('to', 'from', 'cc'):
- result = []
- for nm, addr in getaddresses((val,)):
- nm = str(Header(nm.encode(encoding), encoding))
- try:
- addr = addr.encode('ascii')
- except UnicodeEncodeError: # IDN
- addr = str(Header(addr.encode(encoding), encoding))
- result.append(formataddr((nm, addr)))
- val = ', '.join(result)
+ if name.lower() in ADDRESS_HEADERS:
+ val = ', '.join(sanitize_address(addr, encoding)
+ for addr in getaddresses((val,)))
else:
- val = Header(val.encode(encoding), encoding)
+ val = str(Header(val, encoding))
else:
if name.lower() == 'subject':
val = Header(val)
return name, val
+
+def sanitize_address(addr, encoding):
+ if isinstance(addr, basestring):
+ addr = parseaddr(force_unicode(addr))
+ nm, addr = addr
+ nm = str(Header(nm, encoding))
+ try:
+ addr = addr.encode('ascii')
+ except UnicodeEncodeError: # IDN
+ if u'@' in addr:
+ localpart, domain = addr.split(u'@', 1)
+ localpart = str(Header(localpart, encoding))
+ domain = domain.encode('idna')
+ addr = '@'.join([localpart, domain])
+ else:
+ addr = str(Header(addr, encoding))
+ return formataddr((nm, addr))
+
+
class SafeMIMEText(MIMEText):
-
+
def __init__(self, text, subtype, charset):
self.encoding = charset
MIMEText.__init__(self, text, subtype, charset)
-
- def __setitem__(self, name, val):
+
+ def __setitem__(self, name, val):
name, val = forbid_multi_line_headers(name, val, self.encoding)
MIMEText.__setitem__(self, name, val)
+
class SafeMIMEMultipart(MIMEMultipart):
-
+
def __init__(self, _subtype='mixed', boundary=None, _subparts=None, encoding=None, **_params):
self.encoding = encoding
MIMEMultipart.__init__(self, _subtype, boundary, _subparts, **_params)
-
+
def __setitem__(self, name, val):
name, val = forbid_multi_line_headers(name, val, self.encoding)
MIMEMultipart.__setitem__(self, name, val)
+
class EmailMessage(object):
"""
A container for email information.
@@ -266,7 +297,7 @@ def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
conversions.
"""
super(EmailMultiAlternatives, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers)
- self.alternatives=alternatives or []
+ self.alternatives = alternatives or []
def attach_alternative(self, content, mimetype):
"""Attach an alternative content representation."""
Oops, something went wrong.

0 comments on commit 84db894

Please sign in to comment.