Skip to content

Commit

Permalink
Fixed #6918, #12791: If an email message has an encoding, actually us…
Browse files Browse the repository at this point in the history
…e that encoding to encode body and headers. Thanks for patch with tests oyvind.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@12683 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information
kmtracey committed Mar 5, 2010
1 parent 80545c3 commit 68f4d12
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 15 deletions.
38 changes: 24 additions & 14 deletions django/core/mail/message.py
Expand Up @@ -54,7 +54,8 @@ def make_msgid(idstring=None):
return msgid return msgid




def forbid_multi_line_headers(name, val): def forbid_multi_line_headers(name, val, encoding):
encoding = encoding or settings.DEFAULT_CHARSET
"""Forbids multi-line headers, to prevent header injection.""" """Forbids multi-line headers, to prevent header injection."""
val = force_unicode(val) val = force_unicode(val)
if '\n' in val or '\r' in val: if '\n' in val or '\r' in val:
Expand All @@ -65,29 +66,36 @@ def forbid_multi_line_headers(name, val):
if name.lower() in ('to', 'from', 'cc'): if name.lower() in ('to', 'from', 'cc'):
result = [] result = []
for nm, addr in getaddresses((val,)): 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)))) result.append(formataddr((nm, str(addr))))
val = ', '.join(result) val = ', '.join(result)
else: else:
val = Header(val, settings.DEFAULT_CHARSET) val = Header(val.encode(encoding), encoding)
else: else:
if name.lower() == 'subject': if name.lower() == 'subject':
val = Header(val) val = Header(val)
return name, val return name, val



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



class SafeMIMEMultipart(MIMEMultipart): 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): 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) MIMEMultipart.__setitem__(self, name, val)



class EmailMessage(object): class EmailMessage(object):
""" """
A container for email information. A container for email information.
Expand Down Expand Up @@ -131,7 +139,7 @@ def get_connection(self, fail_silently=False):


def message(self): def message(self):
encoding = self.encoding or settings.DEFAULT_CHARSET 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) self.content_subtype, encoding)
msg = self._create_message(msg) msg = self._create_message(msg)
msg['Subject'] = self.subject msg['Subject'] = self.subject
Expand Down Expand Up @@ -190,8 +198,9 @@ def _create_message(self, msg):


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


def _create_alternatives(self, msg): def _create_alternatives(self, msg):
encoding = self.encoding or settings.DEFAULT_CHARSET
if self.alternatives: if self.alternatives:
body_msg = msg body_msg = msg
msg = SafeMIMEMultipart(_subtype=self.alternative_subtype) msg = SafeMIMEMultipart(_subtype=self.alternative_subtype, encoding=encoding)
if self.body: if self.body:
msg.attach(body_msg) msg.attach(body_msg)
for alternative in self.alternatives: for alternative in self.alternatives:
Expand Down
39 changes: 38 additions & 1 deletion tests/regressiontests/mail/tests.py
Expand Up @@ -112,10 +112,47 @@
>>> email.message()['To'] >>> email.message()['To']
'=?utf-8?q?S=C3=BCrname=2C_Firstname?= <to@example.com>, other@example.com' '=?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().as_string()
'Content-Type: multipart/alternative; boundary="===============...=="\nMIME-Version: 1.0\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: ...\nMessage-ID: <...>\n\n--===============...==\nContent-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.\n--===============...==\nContent-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>\n--===============...==--'
# Handle attachments within an multipart/alternative mail correctly (#9367) # 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, # (test is not as precise/clear as it could be w.r.t. email tree structure,
# but it's good enough.) # but it's good enough.)
>>> headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} >>> headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"}
>>> subject, from_email, to = 'hello', 'from@example.com', 'to@example.com' >>> subject, from_email, to = 'hello', 'from@example.com', 'to@example.com'
>>> text_content = 'This is an important message.' >>> text_content = 'This is an important message.'
Expand Down

0 comments on commit 68f4d12

Please sign in to comment.