mirror of
https://github.com/django/django.git
synced 2025-11-07 07:15:35 +00:00
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.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@15211 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
"""SMTP email backend class."""
|
||||
|
||||
import smtplib
|
||||
import socket
|
||||
import threading
|
||||
@@ -7,6 +6,8 @@ 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 @@ class EmailBackend(BaseEmailBackend):
|
||||
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 email.Utils import formatdate, getaddresses, formataddr
|
||||
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 @@ class EmailMultiAlternatives(EmailMessage):
|
||||
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."""
|
||||
|
||||
Reference in New Issue
Block a user