From 68f4d1256a535e675aa202a2ee917c02371ae70a Mon Sep 17 00:00:00 2001 From: Karen Tracey Date: Fri, 5 Mar 2010 21:27:58 +0000 Subject: [PATCH] Fixed #6918, #12791: If an email message has an encoding, actually use 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 --- django/core/mail/message.py | 38 +++++++++++++++++----------- tests/regressiontests/mail/tests.py | 39 ++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/django/core/mail/message.py b/django/core/mail/message.py index 14d0017311e..273e47a7533 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -54,7 +54,8 @@ def make_msgid(idstring=None): 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.""" val = force_unicode(val) if '\n' in val or '\r' in val: @@ -65,29 +66,36 @@ 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 __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) - 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 EmailMessage(object): """ A container for email information. @@ -131,7 +139,7 @@ class EmailMessage(object): 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 @@ -190,8 +198,9 @@ class EmailMessage(object): 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: @@ -207,8 +216,8 @@ class EmailMessage(object): """ 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) @@ -263,9 +272,10 @@ class EmailMultiAlternatives(EmailMessage): 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: diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py index 79aff356941..96ad83ba387 100644 --- a/tests/regressiontests/mail/tests.py +++ b/tests/regressiontests/mail/tests.py @@ -112,10 +112,47 @@ BadHeaderError: Header values can't contain newlines (got u'Subject\nInjection T >>> email.message()['To'] '=?utf-8?q?S=C3=BCrname=2C_Firstname?= , 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" ','other@example.com']) +>>> email.encoding = 'iso-8859-1' +>>> email.message()['To'] +'=?iso-8859-1?q?S=FCrname=2C_Firstname?= , 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" ' +>>> text_content = 'This is an important message.' +>>> html_content = '

This is an important message.

' +>>> 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?= ' +>>> 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 = '

Firstname Sürname is a great guy.

' +>>> 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

Firstname S=FCrname is a great guy.

\n--===============...==--' + # 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.'