|
1 # coding: utf-8 |
|
2 import email |
|
3 import os |
|
4 import shutil |
|
5 import sys |
|
6 import tempfile |
|
7 from StringIO import StringIO |
|
8 from django.conf import settings |
|
9 from django.core import mail |
|
10 from django.core.mail import EmailMessage, mail_admins, mail_managers, EmailMultiAlternatives |
|
11 from django.core.mail import send_mail, send_mass_mail |
|
12 from django.core.mail.backends.base import BaseEmailBackend |
|
13 from django.core.mail.backends import console, dummy, locmem, filebased, smtp |
|
14 from django.core.mail.message import BadHeaderError |
|
15 from django.test import TestCase |
|
16 from django.utils.translation import ugettext_lazy |
|
17 |
|
18 class MailTests(TestCase): |
|
19 |
|
20 def test_ascii(self): |
|
21 email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com']) |
|
22 message = email.message() |
|
23 self.assertEqual(message['Subject'].encode(), 'Subject') |
|
24 self.assertEqual(message.get_payload(), 'Content') |
|
25 self.assertEqual(message['From'], 'from@example.com') |
|
26 self.assertEqual(message['To'], 'to@example.com') |
|
27 |
|
28 def test_multiple_recipients(self): |
|
29 email = EmailMessage('Subject', 'Content', 'from@example.com', ['to@example.com','other@example.com']) |
|
30 message = email.message() |
|
31 self.assertEqual(message['Subject'].encode(), 'Subject') |
|
32 self.assertEqual(message.get_payload(), 'Content') |
|
33 self.assertEqual(message['From'], 'from@example.com') |
|
34 self.assertEqual(message['To'], 'to@example.com, other@example.com') |
|
35 |
|
36 def test_header_injection(self): |
|
37 email = EmailMessage('Subject\nInjection Test', 'Content', 'from@example.com', ['to@example.com']) |
|
38 self.assertRaises(BadHeaderError, email.message) |
|
39 email = EmailMessage(ugettext_lazy('Subject\nInjection Test'), 'Content', 'from@example.com', ['to@example.com']) |
|
40 self.assertRaises(BadHeaderError, email.message) |
|
41 |
|
42 def test_space_continuation(self): |
|
43 """ |
|
44 Test for space continuation character in long (ascii) subject headers (#7747) |
|
45 """ |
|
46 email = EmailMessage('Long subject lines that get wrapped should use a space continuation character to get expected behaviour in Outlook and Thunderbird', 'Content', 'from@example.com', ['to@example.com']) |
|
47 message = email.message() |
|
48 self.assertEqual(message['Subject'], 'Long subject lines that get wrapped should use a space continuation\n character to get expected behaviour in Outlook and Thunderbird') |
|
49 |
|
50 def test_message_header_overrides(self): |
|
51 """ |
|
52 Specifying dates or message-ids in the extra headers overrides the |
|
53 default values (#9233) |
|
54 """ |
|
55 headers = {"date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} |
|
56 email = EmailMessage('subject', 'content', 'from@example.com', ['to@example.com'], headers=headers) |
|
57 self.assertEqual(email.message().as_string(), 'Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: subject\nFrom: from@example.com\nTo: to@example.com\ndate: Fri, 09 Nov 2001 01:08:47 -0000\nMessage-ID: foo\n\ncontent') |
|
58 |
|
59 def test_empty_admins(self): |
|
60 """ |
|
61 Test that mail_admins/mail_managers doesn't connect to the mail server |
|
62 if there are no recipients (#9383) |
|
63 """ |
|
64 old_admins = settings.ADMINS |
|
65 old_managers = settings.MANAGERS |
|
66 |
|
67 settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')] |
|
68 mail.outbox = [] |
|
69 mail_admins('hi', 'there') |
|
70 self.assertEqual(len(mail.outbox), 1) |
|
71 mail.outbox = [] |
|
72 mail_managers('hi', 'there') |
|
73 self.assertEqual(len(mail.outbox), 1) |
|
74 |
|
75 settings.ADMINS = settings.MANAGERS = [] |
|
76 mail.outbox = [] |
|
77 mail_admins('hi', 'there') |
|
78 self.assertEqual(len(mail.outbox), 0) |
|
79 mail.outbox = [] |
|
80 mail_managers('hi', 'there') |
|
81 self.assertEqual(len(mail.outbox), 0) |
|
82 |
|
83 settings.ADMINS = old_admins |
|
84 settings.MANAGERS = old_managers |
|
85 |
|
86 def test_from_header(self): |
|
87 """ |
|
88 Make sure we can manually set the From header (#9214) |
|
89 """ |
|
90 email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) |
|
91 message = email.message() |
|
92 self.assertEqual(message['From'], 'from@example.com') |
|
93 |
|
94 def test_multiple_message_call(self): |
|
95 """ |
|
96 Regression for #13259 - Make sure that headers are not changed when |
|
97 calling EmailMessage.message() |
|
98 """ |
|
99 email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) |
|
100 message = email.message() |
|
101 self.assertEqual(message['From'], 'from@example.com') |
|
102 message = email.message() |
|
103 self.assertEqual(message['From'], 'from@example.com') |
|
104 |
|
105 def test_unicode_header(self): |
|
106 """ |
|
107 Regression for #11144 - When a to/from/cc header contains unicode, |
|
108 make sure the email addresses are parsed correctly (especially with |
|
109 regards to commas) |
|
110 """ |
|
111 email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Firstname Sürname" <to@example.com>','other@example.com']) |
|
112 self.assertEqual(email.message()['To'], '=?utf-8?q?Firstname_S=C3=BCrname?= <to@example.com>, other@example.com') |
|
113 email = EmailMessage('Subject', 'Content', 'from@example.com', ['"Sürname, Firstname" <to@example.com>','other@example.com']) |
|
114 self.assertEqual(email.message()['To'], '=?utf-8?q?S=C3=BCrname=2C_Firstname?= <to@example.com>, other@example.com') |
|
115 |
|
116 def test_safe_mime_multipart(self): |
|
117 """ |
|
118 Make sure headers can be set with a different encoding than utf-8 in |
|
119 SafeMIMEMultipart as well |
|
120 """ |
|
121 headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} |
|
122 subject, from_email, to = 'hello', 'from@example.com', '"Sürname, Firstname" <to@example.com>' |
|
123 text_content = 'This is an important message.' |
|
124 html_content = '<p>This is an <strong>important</strong> message.</p>' |
|
125 msg = EmailMultiAlternatives('Message from Firstname Sürname', text_content, from_email, [to], headers=headers) |
|
126 msg.attach_alternative(html_content, "text/html") |
|
127 msg.encoding = 'iso-8859-1' |
|
128 self.assertEqual(msg.message()['To'], '=?iso-8859-1?q?S=FCrname=2C_Firstname?= <to@example.com>') |
|
129 self.assertEqual(msg.message()['Subject'].encode(), u'=?iso-8859-1?q?Message_from_Firstname_S=FCrname?=') |
|
130 |
|
131 def test_encoding(self): |
|
132 """ |
|
133 Regression for #12791 - Encode body correctly with other encodings |
|
134 than utf-8 |
|
135 """ |
|
136 email = EmailMessage('Subject', 'Firstname Sürname is a great guy.', 'from@example.com', ['other@example.com']) |
|
137 email.encoding = 'iso-8859-1' |
|
138 message = email.message() |
|
139 self.assertTrue(message.as_string().startswith('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')) |
|
140 self.assertEqual(message.get_payload(), 'Firstname S=FCrname is a great guy.') |
|
141 |
|
142 # Make sure MIME attachments also works correctly with other encodings than utf-8 |
|
143 text_content = 'Firstname Sürname is a great guy.' |
|
144 html_content = '<p>Firstname Sürname is a <strong>great</strong> guy.</p>' |
|
145 msg = EmailMultiAlternatives('Subject', text_content, 'from@example.com', ['to@example.com']) |
|
146 msg.encoding = 'iso-8859-1' |
|
147 msg.attach_alternative(html_content, "text/html") |
|
148 self.assertEqual(msg.message().get_payload(0).as_string(), 'Content-Type: text/plain; charset="iso-8859-1"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\n\nFirstname S=FCrname is a great guy.') |
|
149 self.assertEqual(msg.message().get_payload(1).as_string(), 'Content-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>') |
|
150 |
|
151 def test_attachments(self): |
|
152 """Regression test for #9367""" |
|
153 headers = {"Date": "Fri, 09 Nov 2001 01:08:47 -0000", "Message-ID": "foo"} |
|
154 subject, from_email, to = 'hello', 'from@example.com', 'to@example.com' |
|
155 text_content = 'This is an important message.' |
|
156 html_content = '<p>This is an <strong>important</strong> message.</p>' |
|
157 msg = EmailMultiAlternatives(subject, text_content, from_email, [to], headers=headers) |
|
158 msg.attach_alternative(html_content, "text/html") |
|
159 msg.attach("an attachment.pdf", "%PDF-1.4.%...", mimetype="application/pdf") |
|
160 msg_str = msg.message().as_string() |
|
161 message = email.message_from_string(msg_str) |
|
162 self.assertTrue(message.is_multipart()) |
|
163 self.assertEqual(message.get_content_type(), 'multipart/mixed') |
|
164 self.assertEqual(message.get_default_type(), 'text/plain') |
|
165 payload = message.get_payload() |
|
166 self.assertEqual(payload[0].get_content_type(), 'multipart/alternative') |
|
167 self.assertEqual(payload[1].get_content_type(), 'application/pdf') |
|
168 |
|
169 def test_arbitrary_stream(self): |
|
170 """ |
|
171 Test that the console backend can be pointed at an arbitrary stream. |
|
172 """ |
|
173 s = StringIO() |
|
174 connection = mail.get_connection('django.core.mail.backends.console.EmailBackend', stream=s) |
|
175 send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) |
|
176 self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: ')) |
|
177 |
|
178 def test_stdout(self): |
|
179 """Make sure that the console backend writes to stdout by default""" |
|
180 old_stdout = sys.stdout |
|
181 sys.stdout = StringIO() |
|
182 connection = console.EmailBackend() |
|
183 email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) |
|
184 connection.send_messages([email]) |
|
185 self.assertTrue(sys.stdout.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: ')) |
|
186 sys.stdout = old_stdout |
|
187 |
|
188 def test_dummy(self): |
|
189 """ |
|
190 Make sure that dummy backends returns correct number of sent messages |
|
191 """ |
|
192 connection = dummy.EmailBackend() |
|
193 email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) |
|
194 self.assertEqual(connection.send_messages([email, email, email]), 3) |
|
195 |
|
196 def test_locmem(self): |
|
197 """ |
|
198 Make sure that the locmen backend populates the outbox. |
|
199 """ |
|
200 mail.outbox = [] |
|
201 connection = locmem.EmailBackend() |
|
202 email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) |
|
203 email2 = EmailMessage('Subject 2', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) |
|
204 connection.send_messages([email1, email2]) |
|
205 self.assertEqual(len(mail.outbox), 2) |
|
206 self.assertEqual(mail.outbox[0].subject, 'Subject') |
|
207 self.assertEqual(mail.outbox[1].subject, 'Subject 2') |
|
208 |
|
209 # Make sure that multiple locmem connections share mail.outbox |
|
210 mail.outbox = [] |
|
211 connection2 = locmem.EmailBackend() |
|
212 email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) |
|
213 connection.send_messages([email]) |
|
214 connection2.send_messages([email]) |
|
215 self.assertEqual(len(mail.outbox), 2) |
|
216 |
|
217 def test_file_backend(self): |
|
218 tmp_dir = tempfile.mkdtemp() |
|
219 connection = filebased.EmailBackend(file_path=tmp_dir) |
|
220 email1 = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) |
|
221 connection.send_messages([email1]) |
|
222 self.assertEqual(len(os.listdir(tmp_dir)), 1) |
|
223 message = email.message_from_file(open(os.path.join(tmp_dir, os.listdir(tmp_dir)[0]))) |
|
224 self.assertEqual(message.get_content_type(), 'text/plain') |
|
225 self.assertEqual(message.get('subject'), 'Subject') |
|
226 self.assertEqual(message.get('from'), 'from@example.com') |
|
227 self.assertEqual(message.get('to'), 'to@example.com') |
|
228 connection2 = filebased.EmailBackend(file_path=tmp_dir) |
|
229 connection2.send_messages([email1]) |
|
230 self.assertEqual(len(os.listdir(tmp_dir)), 2) |
|
231 connection.send_messages([email1]) |
|
232 self.assertEqual(len(os.listdir(tmp_dir)), 2) |
|
233 email1.connection = filebased.EmailBackend(file_path=tmp_dir) |
|
234 connection_created = connection.open() |
|
235 email1.send() |
|
236 self.assertEqual(len(os.listdir(tmp_dir)), 3) |
|
237 email1.send() |
|
238 self.assertEqual(len(os.listdir(tmp_dir)), 3) |
|
239 connection.close() |
|
240 shutil.rmtree(tmp_dir) |
|
241 |
|
242 def test_arbitrary_keyword(self): |
|
243 """ |
|
244 Make sure that get_connection() accepts arbitrary keyword that might be |
|
245 used with custom backends. |
|
246 """ |
|
247 c = mail.get_connection(fail_silently=True, foo='bar') |
|
248 self.assertTrue(c.fail_silently) |
|
249 |
|
250 def test_custom_backend(self): |
|
251 """Test custom backend defined in this suite.""" |
|
252 conn = mail.get_connection('regressiontests.mail.custombackend.EmailBackend') |
|
253 self.assertTrue(hasattr(conn, 'test_outbox')) |
|
254 email = EmailMessage('Subject', 'Content', 'bounce@example.com', ['to@example.com'], headers={'From': 'from@example.com'}) |
|
255 conn.send_messages([email]) |
|
256 self.assertEqual(len(conn.test_outbox), 1) |
|
257 |
|
258 def test_backend_arg(self): |
|
259 """Test backend argument of mail.get_connection()""" |
|
260 self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.smtp.EmailBackend'), smtp.EmailBackend)) |
|
261 self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.locmem.EmailBackend'), locmem.EmailBackend)) |
|
262 self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.dummy.EmailBackend'), dummy.EmailBackend)) |
|
263 self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.console.EmailBackend'), console.EmailBackend)) |
|
264 tmp_dir = tempfile.mkdtemp() |
|
265 self.assertTrue(isinstance(mail.get_connection('django.core.mail.backends.filebased.EmailBackend', file_path=tmp_dir), filebased.EmailBackend)) |
|
266 shutil.rmtree(tmp_dir) |
|
267 self.assertTrue(isinstance(mail.get_connection(), locmem.EmailBackend)) |
|
268 |
|
269 def test_connection_arg(self): |
|
270 """Test connection argument to send_mail(), et. al.""" |
|
271 connection = mail.get_connection('django.core.mail.backends.locmem.EmailBackend') |
|
272 |
|
273 mail.outbox = [] |
|
274 send_mail('Subject', 'Content', 'from@example.com', ['to@example.com'], connection=connection) |
|
275 self.assertEqual(len(mail.outbox), 1) |
|
276 message = mail.outbox[0] |
|
277 self.assertEqual(message.subject, 'Subject') |
|
278 self.assertEqual(message.from_email, 'from@example.com') |
|
279 self.assertEqual(message.to, ['to@example.com']) |
|
280 |
|
281 mail.outbox = [] |
|
282 send_mass_mail([ |
|
283 ('Subject1', 'Content1', 'from1@example.com', ['to1@example.com']), |
|
284 ('Subject2', 'Content2', 'from2@example.com', ['to2@example.com']) |
|
285 ], connection=connection) |
|
286 self.assertEqual(len(mail.outbox), 2) |
|
287 message = mail.outbox[0] |
|
288 self.assertEqual(message.subject, 'Subject1') |
|
289 self.assertEqual(message.from_email, 'from1@example.com') |
|
290 self.assertEqual(message.to, ['to1@example.com']) |
|
291 message = mail.outbox[1] |
|
292 self.assertEqual(message.subject, 'Subject2') |
|
293 self.assertEqual(message.from_email, 'from2@example.com') |
|
294 self.assertEqual(message.to, ['to2@example.com']) |
|
295 |
|
296 old_admins = settings.ADMINS |
|
297 old_managers = settings.MANAGERS |
|
298 settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')] |
|
299 |
|
300 mail.outbox = [] |
|
301 mail_admins('Subject', 'Content', connection=connection) |
|
302 self.assertEqual(len(mail.outbox), 1) |
|
303 message = mail.outbox[0] |
|
304 self.assertEqual(message.subject, '[Django] Subject') |
|
305 self.assertEqual(message.from_email, 'root@localhost') |
|
306 self.assertEqual(message.to, ['nobody@example.com']) |
|
307 |
|
308 mail.outbox = [] |
|
309 mail_managers('Subject', 'Content', connection=connection) |
|
310 self.assertEqual(len(mail.outbox), 1) |
|
311 message = mail.outbox[0] |
|
312 self.assertEqual(message.subject, '[Django] Subject') |
|
313 self.assertEqual(message.from_email, 'root@localhost') |
|
314 self.assertEqual(message.to, ['nobody@example.com']) |
|
315 |
|
316 settings.ADMINS = old_admins |
|
317 settings.MANAGERS = old_managers |
|
318 |
|
319 def test_mail_prefix(self): |
|
320 """Test prefix argument in manager/admin mail.""" |
|
321 # Regression for #13494. |
|
322 old_admins = settings.ADMINS |
|
323 old_managers = settings.MANAGERS |
|
324 settings.ADMINS = settings.MANAGERS = [('nobody','nobody@example.com')] |
|
325 |
|
326 mail_managers(ugettext_lazy('Subject'), 'Content') |
|
327 self.assertEqual(len(mail.outbox), 1) |
|
328 message = mail.outbox[0] |
|
329 self.assertEqual(message.subject, '[Django] Subject') |
|
330 |
|
331 mail.outbox = [] |
|
332 mail_admins(ugettext_lazy('Subject'), 'Content') |
|
333 self.assertEqual(len(mail.outbox), 1) |
|
334 message = mail.outbox[0] |
|
335 self.assertEqual(message.subject, '[Django] Subject') |
|
336 |
|
337 settings.ADMINS = old_admins |
|
338 settings.MANAGERS = old_managers |
|
339 |
|
340 def test_idn_validation(self): |
|
341 """Test internationalized email adresses""" |
|
342 # Regression for #14301. |
|
343 mail.outbox = [] |
|
344 from_email = u'fröm@öäü.com' |
|
345 to_email = u'tö@öäü.com' |
|
346 connection = mail.get_connection('django.core.mail.backends.locmem.EmailBackend') |
|
347 send_mail('Subject', 'Content', from_email, [to_email], connection=connection) |
|
348 self.assertEqual(len(mail.outbox), 1) |
|
349 message = mail.outbox[0] |
|
350 self.assertEqual(message.subject, 'Subject') |
|
351 self.assertEqual(message.from_email, from_email) |
|
352 self.assertEqual(message.to, [to_email]) |
|
353 self.assertTrue(message.message().as_string().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: quoted-printable\nSubject: Subject\nFrom: =?utf-8?b?ZnLDtm1Aw7bDpMO8LmNvbQ==?=\nTo: =?utf-8?b?dMO2QMO2w6TDvC5jb20=?=')) |
|
354 |
|
355 def test_idn_smtp_send(self): |
|
356 import smtplib |
|
357 smtplib.SMTP = MockSMTP |
|
358 from_email = u'fröm@öäü.com' |
|
359 to_email = u'tö@öäü.com' |
|
360 connection = mail.get_connection('django.core.mail.backends.smtp.EmailBackend') |
|
361 self.assertTrue(send_mail('Subject', 'Content', from_email, [to_email], connection=connection)) |
|
362 |
|
363 class MockSMTP(object): |
|
364 def __init__(self, host='', port=0, local_hostname=None, |
|
365 timeout=1): |
|
366 pass |
|
367 |
|
368 def sendmail(self, from_addr, to_addrs, msg, mail_options=[], |
|
369 rcpt_options=[]): |
|
370 for addr in to_addrs: |
|
371 str(addr.split('@', 1)[-1]) |
|
372 return {} |
|
373 |
|
374 def quit(self): |
|
375 return 0 |