Skip to content

Commit

Permalink
[1.1.X] Fixed #6918, #12791: If an email message has an encoding, act…
Browse files Browse the repository at this point in the history
…ually use that encoding to encode body and headers. Thanks for patch with tests oyvind.

Backport of r12683 and r12688 from trunk.



git-svn-id: http://code.djangoproject.com/svn/django/branches/releases/1.1.X@12689 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
kmtracey committed Mar 6, 2010
1 parent b3c2ae9 commit daec734
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 11 deletions.
33 changes: 23 additions & 10 deletions django/core/mail.py
Expand Up @@ -69,8 +69,9 @@ def make_msgid(idstring=None):
class BadHeaderError(ValueError):
pass

def forbid_multi_line_headers(name, val):
def forbid_multi_line_headers(name, val, encoding):
"""Forbids multi-line headers, to prevent header injection."""
encoding = encoding or settings.DEFAULT_CHARSET
val = force_unicode(val)
if '\n' in val or '\r' in val:
raise BadHeaderError("Header values can't contain newlines (got %r for header %r)" % (val, name))
Expand All @@ -80,24 +81,34 @@ def forbid_multi_line_headers(name, val):
if name.lower() in ('to', 'from', 'cc'):
result = []
for nm, addr in getaddresses((val,)):
nm = str(Header(nm, settings.DEFAULT_CHARSET))
nm = str(Header(nm.encode(encoding), encoding))
result.append(formataddr((nm, str(addr))))
val = ', '.join(result)
else:
val = Header(val, settings.DEFAULT_CHARSET)
val = Header(val.encode(encoding), encoding)
else:
if name.lower() == 'subject':
val = Header(val)
return name, val

class SafeMIMEText(MIMEText):

def __init__(self, text, subtype, charset):
self.encoding = charset
MIMEText.__init__(self, text, subtype, charset)

def __setitem__(self, name, val):
name, val = forbid_multi_line_headers(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)
name, val = forbid_multi_line_headers(name, val, self.encoding)
MIMEMultipart.__setitem__(self, name, val)

class SMTPConnection(object):
Expand Down Expand Up @@ -234,7 +245,7 @@ def get_connection(self, fail_silently=False):

def message(self):
encoding = self.encoding or settings.DEFAULT_CHARSET
msg = SafeMIMEText(smart_str(self.body, settings.DEFAULT_CHARSET),
msg = SafeMIMEText(smart_str(self.body, encoding),
self.content_subtype, encoding)
msg = self._create_message(msg)
msg['Subject'] = self.subject
Expand Down Expand Up @@ -293,8 +304,9 @@ def _create_message(self, msg):

def _create_attachments(self, msg):
if self.attachments:
encoding = self.encoding or settings.DEFAULT_CHARSET
body_msg = msg
msg = SafeMIMEMultipart(_subtype=self.mixed_subtype)
msg = SafeMIMEMultipart(_subtype=self.mixed_subtype, encoding=encoding)
if self.body:
msg.attach(body_msg)
for attachment in self.attachments:
Expand All @@ -310,8 +322,8 @@ def _create_mime_attachment(self, content, mimetype):
"""
basetype, subtype = mimetype.split('/', 1)
if basetype == 'text':
attachment = SafeMIMEText(smart_str(content,
settings.DEFAULT_CHARSET), subtype, settings.DEFAULT_CHARSET)
encoding = self.encoding or settings.DEFAULT_CHARSET
attachment = SafeMIMEText(smart_str(content, encoding), subtype, encoding)
else:
# Encode non-text attachments with base64.
attachment = MIMEBase(basetype, subtype)
Expand Down Expand Up @@ -365,9 +377,10 @@ def _create_message(self, msg):
return self._create_attachments(self._create_alternatives(msg))

def _create_alternatives(self, msg):
encoding = self.encoding or settings.DEFAULT_CHARSET
if self.alternatives:
body_msg = msg
msg = SafeMIMEMultipart(_subtype=self.alternative_subtype)
msg = SafeMIMEMultipart(_subtype=self.alternative_subtype, encoding=encoding)
if self.body:
msg.attach(body_msg)
for alternative in self.alternatives:
Expand Down
41 changes: 40 additions & 1 deletion tests/regressiontests/mail/tests.py
Expand Up @@ -106,10 +106,49 @@
>>> email.message()['To']
'=?utf-8?q?S=C3=BCrname=2C_Firstname?= <to@example.com>, other@example.com'
# Regression for #6918 - When a header contains unicode,
# make sure headers can be set with a different encoding than utf-8
>>> email = EmailMessage('Message from Firstname Sürname', 'Content', 'from@example.com', ['"Sürname, Firstname" <to@example.com>','other@example.com'])
>>> email.encoding = 'iso-8859-1'
>>> email.message()['To']
'=?iso-8859-1?q?S=FCrname=2C_Firstname?= <to@example.com>, other@example.com'
>>> email.message()['Subject'].encode()
u'=?iso-8859-1?q?Message_from_Firstname_S=FCrname?='
# 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"}
>>> subject, from_email, to = 'hello', 'from@example.com', '"Sürname, Firstname" <to@example.com>'
>>> 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'
>>> msg.message()['To']
'=?iso-8859-1?q?S=FCrname=2C_Firstname?= <to@example.com>'
>>> msg.message()['Subject'].encode()
u'=?iso-8859-1?q?Message_from_Firstname_S=FCrname?='
# 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()
>>> message.as_string()
'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: other@example.com\nDate: ...\nMessage-ID: <...>\n\nFirstname S=FCrname is a great guy.'
# Make sure MIME attachments also 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")
>>> msg.message().get_payload(0).as_string()
'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.'
>>> msg.message().get_payload(1).as_string()
'Content-Type: text/html; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\n<p>Firstname S=FCrname is a <strong>great</strong> guy.</p>'
# Handle attachments within an multipart/alternative mail correctly (#9367)
# (test is not as precise/clear as it could be w.r.t. email tree structure,
# but it's good enough.)
>>> 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.'
Expand Down

0 comments on commit daec734

Please sign in to comment.