Fixed #18967 -- Don't base64-encode message/rfc822 attachments.
Thanks Michael Farrell for the report and his work on the fix.
This commit is contained in:
parent
839940f27f
commit
01223840f3
|
@ -4,11 +4,13 @@ import mimetypes
|
|||
import os
|
||||
import random
|
||||
import time
|
||||
from email import charset as Charset, encoders as Encoders
|
||||
from email import charset as Charset, encoders as Encoders, message_from_string
|
||||
from email.generator import Generator
|
||||
from email.message import Message
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.message import MIMEMessage
|
||||
from email.header import Header
|
||||
from email.utils import formatdate, getaddresses, formataddr, parseaddr
|
||||
|
||||
|
@ -118,6 +120,27 @@ def sanitize_address(addr, encoding):
|
|||
return formataddr((nm, addr))
|
||||
|
||||
|
||||
class SafeMIMEMessage(MIMEMessage):
|
||||
|
||||
def __setitem__(self, name, val):
|
||||
# message/rfc822 attachments must be ASCII
|
||||
name, val = forbid_multi_line_headers(name, val, 'ascii')
|
||||
MIMEMessage.__setitem__(self, name, val)
|
||||
|
||||
def as_string(self, unixfrom=False):
|
||||
"""Return the entire formatted message as a string.
|
||||
Optional `unixfrom' when True, means include the Unix From_ envelope
|
||||
header.
|
||||
|
||||
This overrides the default as_string() implementation to not mangle
|
||||
lines that begin with 'From '. See bug #13433 for details.
|
||||
"""
|
||||
fp = six.StringIO()
|
||||
g = Generator(fp, mangle_from_=False)
|
||||
g.flatten(self, unixfrom=unixfrom)
|
||||
return fp.getvalue()
|
||||
|
||||
|
||||
class SafeMIMEText(MIMEText):
|
||||
|
||||
def __init__(self, text, subtype, charset):
|
||||
|
@ -137,7 +160,7 @@ class SafeMIMEText(MIMEText):
|
|||
lines that begin with 'From '. See bug #13433 for details.
|
||||
"""
|
||||
fp = six.StringIO()
|
||||
g = Generator(fp, mangle_from_ = False)
|
||||
g = Generator(fp, mangle_from_=False)
|
||||
g.flatten(self, unixfrom=unixfrom)
|
||||
return fp.getvalue()
|
||||
|
||||
|
@ -161,7 +184,7 @@ class SafeMIMEMultipart(MIMEMultipart):
|
|||
lines that begin with 'From '. See bug #13433 for details.
|
||||
"""
|
||||
fp = six.StringIO()
|
||||
g = Generator(fp, mangle_from_ = False)
|
||||
g = Generator(fp, mangle_from_=False)
|
||||
g.flatten(self, unixfrom=unixfrom)
|
||||
return fp.getvalue()
|
||||
|
||||
|
@ -292,11 +315,26 @@ class EmailMessage(object):
|
|||
def _create_mime_attachment(self, content, mimetype):
|
||||
"""
|
||||
Converts the content, mimetype pair into a MIME attachment object.
|
||||
|
||||
If the mimetype is message/rfc822, content may be an
|
||||
email.Message or EmailMessage object, as well as a str.
|
||||
"""
|
||||
basetype, subtype = mimetype.split('/', 1)
|
||||
if basetype == 'text':
|
||||
encoding = self.encoding or settings.DEFAULT_CHARSET
|
||||
attachment = SafeMIMEText(content, subtype, encoding)
|
||||
elif basetype == 'message' and subtype == 'rfc822':
|
||||
# Bug #18967: per RFC2046 s5.2.1, message/rfc822 attachments
|
||||
# must not be base64 encoded.
|
||||
if isinstance(content, EmailMessage):
|
||||
# convert content into an email.Message first
|
||||
content = content.message()
|
||||
elif not isinstance(content, Message):
|
||||
# For compatibility with existing code, parse the message
|
||||
# into a email.Message object if it is not one already.
|
||||
content = message_from_string(content)
|
||||
|
||||
attachment = SafeMIMEMessage(content, subtype)
|
||||
else:
|
||||
# Encode non-text attachments with base64.
|
||||
attachment = MIMEBase(basetype, subtype)
|
||||
|
|
|
@ -319,6 +319,18 @@ The class has the following methods:
|
|||
|
||||
message.attach('design.png', img_data, 'image/png')
|
||||
|
||||
.. versionchanged:: 1.7
|
||||
|
||||
If you specify a ``mimetype`` of ``message/rfc822``, it will also accept
|
||||
:class:`django.core.mail.EmailMessage` and :py:class:`email.message.Message`.
|
||||
|
||||
In addition, ``message/rfc822`` attachments will no longer be
|
||||
base64-encoded in violation of :rfc:`2046#section-5.2.1`, which can cause
|
||||
issues with displaying the attachments in `Evolution`__ and `Thunderbird`__.
|
||||
|
||||
__ https://bugzilla.gnome.org/show_bug.cgi?id=651197
|
||||
__ https://bugzilla.mozilla.org/show_bug.cgi?id=333880
|
||||
|
||||
* ``attach_file()`` creates a new attachment using a file from your
|
||||
filesystem. Call it with the path of the file to attach and, optionally,
|
||||
the MIME type to use for the attachment. If the MIME type is omitted, it
|
||||
|
@ -326,8 +338,6 @@ The class has the following methods:
|
|||
|
||||
message.attach_file('/images/weather_map.png')
|
||||
|
||||
.. _DEFAULT_FROM_EMAIL: ../settings/#default-from-email
|
||||
|
||||
Sending alternative content types
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -331,6 +331,39 @@ class MailTests(TestCase):
|
|||
self.assertFalse(str('Content-Transfer-Encoding: quoted-printable') in s)
|
||||
self.assertTrue(str('Content-Transfer-Encoding: 8bit') in s)
|
||||
|
||||
def test_dont_base64_encode_message_rfc822(self):
|
||||
# Ticket #18967
|
||||
# Shouldn't use base64 encoding for a child EmailMessage attachment.
|
||||
# Create a child message first
|
||||
child_msg = EmailMessage('Child Subject', 'Some body of child message', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||
child_s = child_msg.message().as_string()
|
||||
|
||||
# Now create a parent
|
||||
parent_msg = EmailMessage('Parent Subject', 'Some parent body', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||
|
||||
# Attach to parent as a string
|
||||
parent_msg.attach(content=child_s, mimetype='message/rfc822')
|
||||
parent_s = parent_msg.message().as_string()
|
||||
|
||||
# Verify that the child message header is not base64 encoded
|
||||
self.assertTrue(str('Child Subject') in parent_s)
|
||||
|
||||
# Feature test: try attaching email.Message object directly to the mail.
|
||||
parent_msg = EmailMessage('Parent Subject', 'Some parent body', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||
parent_msg.attach(content=child_msg.message(), mimetype='message/rfc822')
|
||||
parent_s = parent_msg.message().as_string()
|
||||
|
||||
# Verify that the child message header is not base64 encoded
|
||||
self.assertTrue(str('Child Subject') in parent_s)
|
||||
|
||||
# Feature test: try attaching Django's EmailMessage object directly to the mail.
|
||||
parent_msg = EmailMessage('Parent Subject', 'Some parent body', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'})
|
||||
parent_msg.attach(content=child_msg, mimetype='message/rfc822')
|
||||
parent_s = parent_msg.message().as_string()
|
||||
|
||||
# Verify that the child message header is not base64 encoded
|
||||
self.assertTrue(str('Child Subject') in parent_s)
|
||||
|
||||
|
||||
class BaseEmailBackendTests(object):
|
||||
email_backend = None
|
||||
|
|
Loading…
Reference in New Issue