diff --git a/django/core/mail/message.py b/django/core/mail/message.py index a3b36df832..41de859ee1 100644 --- a/django/core/mail/message.py +++ b/django/core/mail/message.py @@ -5,8 +5,8 @@ import os import random import sys import time +from email import generator from email import charset as Charset, encoders as Encoders -from email.generator import Generator from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from email.mime.base import MIMEBase @@ -119,7 +119,34 @@ def sanitize_address(addr, encoding): return formataddr((nm, addr)) -class SafeMIMEText(MIMEText): +class MIMEMixin(): + 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. + """ + # Using a normal Generator on python 3 will yield a string, which will + # get base64 encoded in some cases to ensure that it's always convertable + # to ascii. We don't want base64 encoded emails, so we use a BytesGenertor + # which will do the right thing and then decode according to our known + # encoding. See #21093 and #3472 for details. + if six.PY3 and sys.version_info >= (3, 3, 3): + fp = six.BytesIO() + g = generator.BytesGenerator(fp, mangle_from_=False) + g.flatten(self, unixfrom=unixfrom) + encoding = self.get_charset().get_output_charset() if self.get_charset() else 'utf-8' + return fp.getvalue().decode(encoding) + else: + fp = six.StringIO() + g = generator.Generator(fp, mangle_from_=False) + g.flatten(self, unixfrom=unixfrom) + return fp.getvalue() + + +class SafeMIMEText(MIMEMixin, MIMEText): def __init__(self, text, subtype, charset): self.encoding = charset @@ -129,24 +156,8 @@ class SafeMIMEText(MIMEText): name, val = forbid_multi_line_headers(name, val, self.encoding) MIMEText.__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) - if sys.version_info < (2, 6, 6) and isinstance(self._payload, six.text_type): - # Workaround for http://bugs.python.org/issue1368247 - self._payload = self._payload.encode(self._charset.output_charset) - g.flatten(self, unixfrom=unixfrom) - return fp.getvalue() - - -class SafeMIMEMultipart(MIMEMultipart): +class SafeMIMEMultipart(MIMEMixin, MIMEMultipart): def __init__(self, _subtype='mixed', boundary=None, _subparts=None, encoding=None, **_params): self.encoding = encoding @@ -156,19 +167,6 @@ class SafeMIMEMultipart(MIMEMultipart): name, val = forbid_multi_line_headers(name, val, self.encoding) MIMEMultipart.__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 EmailMessage(object): """