Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #21093 -- Ensured that mails are not base64 encoded on python 3…

….3.3+.

Thanks to Arfrever for the report and Aymeric for the review.
  • Loading branch information...
commit f28ea0230846c81e9b6725454bb3a10df24678aa 1 parent b7a7baa
Florian Apolloner authored December 28, 2013

Showing 1 changed file with 30 additions and 41 deletions. Show diff stats Hide diff stats

  1. 71  django/core/mail/message.py
71  django/core/mail/message.py
@@ -3,9 +3,10 @@
3 3
 import mimetypes
4 4
 import os
5 5
 import random
  6
+import sys
6 7
 import time
7  
-from email import charset as Charset, encoders as Encoders, message_from_string
8  
-from email.generator import Generator
  8
+from email import (charset as Charset, encoders as Encoders,
  9
+    message_from_string, generator)
9 10
 from email.message import Message
10 11
 from email.mime.text import MIMEText
11 12
 from email.mime.multipart import MIMEMultipart
@@ -121,13 +122,7 @@ def sanitize_address(addr, encoding):
121 122
     return formataddr((nm, addr))
122 123
 
123 124
 
124  
-class SafeMIMEMessage(MIMEMessage):
125  
-
126  
-    def __setitem__(self, name, val):
127  
-        # message/rfc822 attachments must be ASCII
128  
-        name, val = forbid_multi_line_headers(name, val, 'ascii')
129  
-        MIMEMessage.__setitem__(self, name, val)
130  
-
  125
+class MIMEMixin():
131 126
     def as_string(self, unixfrom=False):
132 127
         """Return the entire formatted message as a string.
133 128
         Optional `unixfrom' when True, means include the Unix From_ envelope
@@ -136,13 +131,33 @@ def as_string(self, unixfrom=False):
136 131
         This overrides the default as_string() implementation to not mangle
137 132
         lines that begin with 'From '. See bug #13433 for details.
138 133
         """
139  
-        fp = six.StringIO()
140  
-        g = Generator(fp, mangle_from_=False)
141  
-        g.flatten(self, unixfrom=unixfrom)
142  
-        return fp.getvalue()
  134
+        # Using a normal Generator on python 3 will yield a string, which will
  135
+        # get base64 encoded in some cases to ensure that it's always convertable
  136
+        # to ascii. We don't want base64 encoded emails, so we use a BytesGenertor
  137
+        # which will do the right thing and then decode according to our known
  138
+        # encoding. See #21093 and #3472 for details.
  139
+        if six.PY3 and sys.version_info >= (3, 3, 3):
  140
+            fp = six.BytesIO()
  141
+            g = generator.BytesGenerator(fp, mangle_from_=False)
  142
+            g.flatten(self, unixfrom=unixfrom)
  143
+            encoding = self.get_charset().get_output_charset() if self.get_charset() else 'utf-8'
  144
+            return fp.getvalue().decode(encoding)
  145
+        else:
  146
+            fp = six.StringIO()
  147
+            g = generator.Generator(fp, mangle_from_=False)
  148
+            g.flatten(self, unixfrom=unixfrom)
  149
+            return fp.getvalue()
  150
+
143 151
 
  152
+class SafeMIMEMessage(MIMEMixin, MIMEMessage):
144 153
 
145  
-class SafeMIMEText(MIMEText):
  154
+    def __setitem__(self, name, val):
  155
+        # message/rfc822 attachments must be ASCII
  156
+        name, val = forbid_multi_line_headers(name, val, 'ascii')
  157
+        MIMEMessage.__setitem__(self, name, val)
  158
+
  159
+
  160
+class SafeMIMEText(MIMEMixin, MIMEText):
146 161
 
147 162
     def __init__(self, text, subtype, charset):
148 163
         self.encoding = charset
@@ -161,21 +176,8 @@ def __setitem__(self, name, val):
161 176
         name, val = forbid_multi_line_headers(name, val, self.encoding)
162 177
         MIMEText.__setitem__(self, name, val)
163 178
 
164  
-    def as_string(self, unixfrom=False):
165  
-        """Return the entire formatted message as a string.
166  
-        Optional `unixfrom' when True, means include the Unix From_ envelope
167  
-        header.
168  
-
169  
-        This overrides the default as_string() implementation to not mangle
170  
-        lines that begin with 'From '. See bug #13433 for details.
171  
-        """
172  
-        fp = six.StringIO()
173  
-        g = Generator(fp, mangle_from_=False)
174  
-        g.flatten(self, unixfrom=unixfrom)
175  
-        return fp.getvalue()
176  
-
177 179
 
178  
-class SafeMIMEMultipart(MIMEMultipart):
  180
+class SafeMIMEMultipart(MIMEMixin, MIMEMultipart):
179 181
 
180 182
     def __init__(self, _subtype='mixed', boundary=None, _subparts=None, encoding=None, **_params):
181 183
         self.encoding = encoding
@@ -185,19 +187,6 @@ def __setitem__(self, name, val):
185 187
         name, val = forbid_multi_line_headers(name, val, self.encoding)
186 188
         MIMEMultipart.__setitem__(self, name, val)
187 189
 
188  
-    def as_string(self, unixfrom=False):
189  
-        """Return the entire formatted message as a string.
190  
-        Optional `unixfrom' when True, means include the Unix From_ envelope
191  
-        header.
192  
-
193  
-        This overrides the default as_string() implementation to not mangle
194  
-        lines that begin with 'From '. See bug #13433 for details.
195  
-        """
196  
-        fp = six.StringIO()
197  
-        g = Generator(fp, mangle_from_=False)
198  
-        g.flatten(self, unixfrom=unixfrom)
199  
-        return fp.getvalue()
200  
-
201 190
 
202 191
 class EmailMessage(object):
203 192
     """

0 notes on commit f28ea02

Please sign in to comment.
Something went wrong with that request. Please try again.