Permalink
Browse files

Fixed #15042 -- Ensured that email addresses without a domain can sti…

…ll 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.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@15211 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
1 parent 0d70d29 commit 11997218eeed02c8be7a81b87db321464839a2cf @freakboy3742 freakboy3742 committed Jan 15, 2011
Showing with 474 additions and 248 deletions.
  1. +5 −8 django/core/mail/backends/smtp.py
  2. +48 −17 django/core/mail/message.py
  3. +421 −223 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.
@@ -274,7 +305,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, cc)
- 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 1199721

Please sign in to comment.