blob: b7eb527a8fe55494b778dd67476aa883ff730c06 [file] [log] [blame]
Barry Warsaw763af412002-01-27 06:48:47 +00001# Copyright (C) 2001,2002 Python Software Foundation
Barry Warsaw41075852001-09-23 03:18:13 +00002# email package unit tests
3
Barry Warsaw409a4c02002-04-10 21:01:31 +00004import sys
Barry Warsaw41075852001-09-23 03:18:13 +00005import os
6import time
7import unittest
8import base64
Barry Warsawb6a92132002-06-28 23:49:33 +00009import difflib
Barry Warsaw41075852001-09-23 03:18:13 +000010from cStringIO import StringIO
Barry Warsaw2c685062002-06-02 19:09:27 +000011from types import StringType, ListType
Barry Warsaw409a4c02002-04-10 21:01:31 +000012import warnings
Barry Warsaw41075852001-09-23 03:18:13 +000013
14import email
15
Barry Warsaw409a4c02002-04-10 21:01:31 +000016from email.Charset import Charset
17from email.Header import Header, decode_header
Barry Warsawbf7a59d2001-10-11 15:44:50 +000018from email.Parser import Parser, HeaderParser
Barry Warsaw41075852001-09-23 03:18:13 +000019from email.Generator import Generator, DecodedGenerator
20from email.Message import Message
Barry Warsawfee435a2001-10-09 19:23:57 +000021from email.MIMEAudio import MIMEAudio
Barry Warsaw65279d02001-09-26 05:47:08 +000022from email.MIMEText import MIMEText
23from email.MIMEImage import MIMEImage
Barry Warsaw41075852001-09-23 03:18:13 +000024from email.MIMEBase import MIMEBase
Barry Warsaw65279d02001-09-26 05:47:08 +000025from email.MIMEMessage import MIMEMessage
Barry Warsaw329d3af2002-07-09 02:38:24 +000026from email.MIMEMultipart import MIMEMultipart
Barry Warsaw41075852001-09-23 03:18:13 +000027from email import Utils
28from email import Errors
29from email import Encoders
30from email import Iterators
Barry Warsaw409a4c02002-04-10 21:01:31 +000031from email import base64MIME
32from email import quopriMIME
Barry Warsaw41075852001-09-23 03:18:13 +000033
Barry Warsawc9ad32c2002-04-15 22:14:06 +000034import test_support
Guido van Rossum78f0dd32001-12-07 21:07:08 +000035from test_support import findfile, __file__ as test_support_file
Barry Warsaw41075852001-09-23 03:18:13 +000036
Barry Warsawc9ad32c2002-04-15 22:14:06 +000037
Barry Warsaw41075852001-09-23 03:18:13 +000038NL = '\n'
39EMPTYSTRING = ''
Barry Warsaw07227d12001-10-17 20:52:26 +000040SPACE = ' '
Barry Warsaw41075852001-09-23 03:18:13 +000041
Barry Warsaw409a4c02002-04-10 21:01:31 +000042# We don't care about DeprecationWarnings
43warnings.filterwarnings('ignore', '', DeprecationWarning, __name__)
44
Barry Warsaw41075852001-09-23 03:18:13 +000045
Barry Warsaw08a534d2001-10-04 17:58:50 +000046
Barry Warsaw41075852001-09-23 03:18:13 +000047def openfile(filename):
Guido van Rossum78f0dd32001-12-07 21:07:08 +000048 path = os.path.join(os.path.dirname(test_support_file), 'data', filename)
Barry Warsaw41075852001-09-23 03:18:13 +000049 return open(path)
50
51
Barry Warsaw08a534d2001-10-04 17:58:50 +000052
Barry Warsaw41075852001-09-23 03:18:13 +000053# Base test class
54class TestEmailBase(unittest.TestCase):
Barry Warsaw329d3af2002-07-09 02:38:24 +000055 if hasattr(difflib, 'ndiff'):
56 # Python 2.2 and beyond
57 def ndiffAssertEqual(self, first, second):
58 """Like failUnlessEqual except use ndiff for readable output."""
59 if first <> second:
60 diff = difflib.ndiff(first.splitlines(), second.splitlines())
61 fp = StringIO()
62 print >> fp, NL, NL.join(diff)
63 raise self.failureException, fp.getvalue()
64 else:
65 # Python 2.1
66 ndiffAssertEqual = unittest.TestCase.assertEqual
Barry Warsawb6a92132002-06-28 23:49:33 +000067
Barry Warsaw41075852001-09-23 03:18:13 +000068 def _msgobj(self, filename):
Barry Warsaw409a4c02002-04-10 21:01:31 +000069 fp = openfile(findfile(filename))
Barry Warsaw41075852001-09-23 03:18:13 +000070 try:
Barry Warsaw65279d02001-09-26 05:47:08 +000071 msg = email.message_from_file(fp)
Barry Warsaw41075852001-09-23 03:18:13 +000072 finally:
73 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +000074 return msg
Barry Warsaw41075852001-09-23 03:18:13 +000075
76
Barry Warsaw08a534d2001-10-04 17:58:50 +000077
Barry Warsaw41075852001-09-23 03:18:13 +000078# Test various aspects of the Message class's API
79class TestMessageAPI(TestEmailBase):
Barry Warsaw2f6a0b02001-10-09 15:49:35 +000080 def test_get_all(self):
81 eq = self.assertEqual
82 msg = self._msgobj('msg_20.txt')
83 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
84 eq(msg.get_all('xx', 'n/a'), 'n/a')
85
Barry Warsaw409a4c02002-04-10 21:01:31 +000086 def test_getset_charset(self):
87 eq = self.assertEqual
88 msg = Message()
89 eq(msg.get_charset(), None)
90 charset = Charset('iso-8859-1')
91 msg.set_charset(charset)
92 eq(msg['mime-version'], '1.0')
93 eq(msg.get_type(), 'text/plain')
94 eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
95 eq(msg.get_param('charset'), 'iso-8859-1')
96 eq(msg['content-transfer-encoding'], 'quoted-printable')
97 eq(msg.get_charset().input_charset, 'iso-8859-1')
98 # Remove the charset
99 msg.set_charset(None)
100 eq(msg.get_charset(), None)
101 eq(msg['content-type'], 'text/plain')
102 # Try adding a charset when there's already MIME headers present
103 msg = Message()
104 msg['MIME-Version'] = '2.0'
105 msg['Content-Type'] = 'text/x-weird'
106 msg['Content-Transfer-Encoding'] = 'quinted-puntable'
107 msg.set_charset(charset)
108 eq(msg['mime-version'], '2.0')
109 eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
110 eq(msg['content-transfer-encoding'], 'quinted-puntable')
111
112 def test_set_charset_from_string(self):
113 eq = self.assertEqual
114 msg = Message()
115 msg.set_charset('us-ascii')
116 eq(msg.get_charset().input_charset, 'us-ascii')
117 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
118
119 def test_set_payload_with_charset(self):
120 msg = Message()
121 charset = Charset('iso-8859-1')
122 msg.set_payload('This is a string payload', charset)
123 self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
124
Barry Warsaw41075852001-09-23 03:18:13 +0000125 def test_get_charsets(self):
126 eq = self.assertEqual
Tim Peters527e64f2001-10-04 05:36:56 +0000127
Barry Warsaw65279d02001-09-26 05:47:08 +0000128 msg = self._msgobj('msg_08.txt')
129 charsets = msg.get_charsets()
130 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000131
Barry Warsaw65279d02001-09-26 05:47:08 +0000132 msg = self._msgobj('msg_09.txt')
133 charsets = msg.get_charsets('dingbat')
134 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
135 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000136
Barry Warsaw65279d02001-09-26 05:47:08 +0000137 msg = self._msgobj('msg_12.txt')
138 charsets = msg.get_charsets()
139 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
140 'iso-8859-3', 'us-ascii', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000141
142 def test_get_filename(self):
143 eq = self.assertEqual
144
Barry Warsaw65279d02001-09-26 05:47:08 +0000145 msg = self._msgobj('msg_04.txt')
146 filenames = [p.get_filename() for p in msg.get_payload()]
Barry Warsaw41075852001-09-23 03:18:13 +0000147 eq(filenames, ['msg.txt', 'msg.txt'])
148
Barry Warsaw65279d02001-09-26 05:47:08 +0000149 msg = self._msgobj('msg_07.txt')
150 subpart = msg.get_payload(1)
Barry Warsaw41075852001-09-23 03:18:13 +0000151 eq(subpart.get_filename(), 'dingusfish.gif')
152
153 def test_get_boundary(self):
154 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000155 msg = self._msgobj('msg_07.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000156 # No quotes!
Barry Warsaw65279d02001-09-26 05:47:08 +0000157 eq(msg.get_boundary(), 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000158
159 def test_set_boundary(self):
160 eq = self.assertEqual
161 # This one has no existing boundary parameter, but the Content-Type:
162 # header appears fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000163 msg = self._msgobj('msg_01.txt')
164 msg.set_boundary('BOUNDARY')
165 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000166 eq(header.lower(), 'content-type')
Barry Warsaw9546e792002-06-29 05:58:45 +0000167 eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
Barry Warsaw41075852001-09-23 03:18:13 +0000168 # This one has a Content-Type: header, with a boundary, stuck in the
169 # middle of its headers. Make sure the order is preserved; it should
170 # be fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000171 msg = self._msgobj('msg_04.txt')
172 msg.set_boundary('BOUNDARY')
173 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000174 eq(header.lower(), 'content-type')
175 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
176 # And this one has no Content-Type: header at all.
Barry Warsaw65279d02001-09-26 05:47:08 +0000177 msg = self._msgobj('msg_03.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000178 self.assertRaises(Errors.HeaderParseError,
Barry Warsaw65279d02001-09-26 05:47:08 +0000179 msg.set_boundary, 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000180
181 def test_get_decoded_payload(self):
182 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000183 msg = self._msgobj('msg_10.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000184 # The outer message is a multipart
Barry Warsaw65279d02001-09-26 05:47:08 +0000185 eq(msg.get_payload(decode=1), None)
Barry Warsaw41075852001-09-23 03:18:13 +0000186 # Subpart 1 is 7bit encoded
Barry Warsaw65279d02001-09-26 05:47:08 +0000187 eq(msg.get_payload(0).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000188 'This is a 7bit encoded message.\n')
189 # Subpart 2 is quopri
Barry Warsaw65279d02001-09-26 05:47:08 +0000190 eq(msg.get_payload(1).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000191 '\xa1This is a Quoted Printable encoded message!\n')
192 # Subpart 3 is base64
Barry Warsaw65279d02001-09-26 05:47:08 +0000193 eq(msg.get_payload(2).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000194 'This is a Base64 encoded message.')
195 # Subpart 4 has no Content-Transfer-Encoding: header.
Barry Warsaw65279d02001-09-26 05:47:08 +0000196 eq(msg.get_payload(3).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000197 'This has no Content-Transfer-Encoding: header.\n')
198
199 def test_decoded_generator(self):
200 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000201 msg = self._msgobj('msg_07.txt')
202 fp = openfile('msg_17.txt')
203 try:
204 text = fp.read()
205 finally:
206 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +0000207 s = StringIO()
208 g = DecodedGenerator(s)
Barry Warsaw2c685062002-06-02 19:09:27 +0000209 g.flatten(msg)
Barry Warsaw65279d02001-09-26 05:47:08 +0000210 eq(s.getvalue(), text)
Barry Warsaw41075852001-09-23 03:18:13 +0000211
212 def test__contains__(self):
213 msg = Message()
214 msg['From'] = 'Me'
215 msg['to'] = 'You'
216 # Check for case insensitivity
217 self.failUnless('from' in msg)
218 self.failUnless('From' in msg)
219 self.failUnless('FROM' in msg)
220 self.failUnless('to' in msg)
221 self.failUnless('To' in msg)
222 self.failUnless('TO' in msg)
223
224 def test_as_string(self):
225 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000226 msg = self._msgobj('msg_01.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000227 fp = openfile('msg_01.txt')
228 try:
229 text = fp.read()
230 finally:
231 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000232 eq(text, msg.as_string())
233 fullrepr = str(msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000234 lines = fullrepr.split('\n')
235 self.failUnless(lines[0].startswith('From '))
236 eq(text, NL.join(lines[1:]))
237
238 def test_bad_param(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000239 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000240 self.assertEqual(msg.get_param('baz'), '')
241
242 def test_missing_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000243 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000244 self.assertEqual(msg.get_filename(), None)
245
246 def test_bogus_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000247 msg = email.message_from_string(
248 "Content-Disposition: blarg; filename\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000249 self.assertEqual(msg.get_filename(), '')
Tim Peters527e64f2001-10-04 05:36:56 +0000250
Barry Warsaw41075852001-09-23 03:18:13 +0000251 def test_missing_boundary(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000252 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000253 self.assertEqual(msg.get_boundary(), None)
254
Barry Warsaw65279d02001-09-26 05:47:08 +0000255 def test_get_params(self):
256 eq = self.assertEqual
257 msg = email.message_from_string(
258 'X-Header: foo=one; bar=two; baz=three\n')
259 eq(msg.get_params(header='x-header'),
260 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
261 msg = email.message_from_string(
262 'X-Header: foo; bar=one; baz=two\n')
263 eq(msg.get_params(header='x-header'),
264 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
265 eq(msg.get_params(), None)
266 msg = email.message_from_string(
267 'X-Header: foo; bar="one"; baz=two\n')
268 eq(msg.get_params(header='x-header'),
269 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
270
Barry Warsaw409a4c02002-04-10 21:01:31 +0000271 def test_get_param_liberal(self):
272 msg = Message()
273 msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
274 self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
275
Barry Warsaw65279d02001-09-26 05:47:08 +0000276 def test_get_param(self):
277 eq = self.assertEqual
278 msg = email.message_from_string(
279 "X-Header: foo=one; bar=two; baz=three\n")
280 eq(msg.get_param('bar', header='x-header'), 'two')
281 eq(msg.get_param('quuz', header='x-header'), None)
282 eq(msg.get_param('quuz'), None)
283 msg = email.message_from_string(
284 'X-Header: foo; bar="one"; baz=two\n')
285 eq(msg.get_param('foo', header='x-header'), '')
286 eq(msg.get_param('bar', header='x-header'), 'one')
287 eq(msg.get_param('baz', header='x-header'), 'two')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000288 # XXX: We are not RFC-2045 compliant! We cannot parse:
289 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
290 # msg.get_param("weird")
291 # yet.
Barry Warsaw65279d02001-09-26 05:47:08 +0000292
Barry Warsaw2539cf52001-10-25 22:43:46 +0000293 def test_get_param_funky_continuation_lines(self):
294 msg = self._msgobj('msg_22.txt')
295 self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
296
Barry Warsaw65279d02001-09-26 05:47:08 +0000297 def test_has_key(self):
298 msg = email.message_from_string('Header: exists')
299 self.failUnless(msg.has_key('header'))
300 self.failUnless(msg.has_key('Header'))
301 self.failUnless(msg.has_key('HEADER'))
302 self.failIf(msg.has_key('headeri'))
303
Barry Warsaw409a4c02002-04-10 21:01:31 +0000304 def test_set_param(self):
305 eq = self.assertEqual
306 msg = Message()
307 msg.set_param('charset', 'iso-2022-jp')
308 eq(msg.get_param('charset'), 'iso-2022-jp')
309 msg.set_param('importance', 'high value')
310 eq(msg.get_param('importance'), 'high value')
311 eq(msg.get_param('importance', unquote=0), '"high value"')
312 eq(msg.get_params(), [('text/plain', ''),
313 ('charset', 'iso-2022-jp'),
314 ('importance', 'high value')])
315 eq(msg.get_params(unquote=0), [('text/plain', ''),
316 ('charset', '"iso-2022-jp"'),
317 ('importance', '"high value"')])
318 msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
319 eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
Barry Warsaw41075852001-09-23 03:18:13 +0000320
Barry Warsaw409a4c02002-04-10 21:01:31 +0000321 def test_del_param(self):
322 eq = self.assertEqual
323 msg = self._msgobj('msg_05.txt')
324 eq(msg.get_params(),
325 [('multipart/report', ''), ('report-type', 'delivery-status'),
326 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
327 old_val = msg.get_param("report-type")
328 msg.del_param("report-type")
329 eq(msg.get_params(),
330 [('multipart/report', ''),
Barry Warsaw16f90552002-04-16 05:06:42 +0000331 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
Barry Warsaw409a4c02002-04-10 21:01:31 +0000332 msg.set_param("report-type", old_val)
333 eq(msg.get_params(),
334 [('multipart/report', ''),
335 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
336 ('report-type', old_val)])
337
338 def test_set_type(self):
339 eq = self.assertEqual
340 msg = Message()
341 self.assertRaises(ValueError, msg.set_type, 'text')
342 msg.set_type('text/plain')
343 eq(msg['content-type'], 'text/plain')
344 msg.set_param('charset', 'us-ascii')
345 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
346 msg.set_type('text/html')
347 eq(msg['content-type'], 'text/html; charset="us-ascii"')
348
Barry Warsaw16f90552002-04-16 05:06:42 +0000349
Barry Warsaw08a534d2001-10-04 17:58:50 +0000350
Barry Warsaw41075852001-09-23 03:18:13 +0000351# Test the email.Encoders module
352class TestEncoders(unittest.TestCase):
353 def test_encode_noop(self):
354 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000355 msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
Barry Warsaw41075852001-09-23 03:18:13 +0000356 eq(msg.get_payload(), 'hello world\n')
Barry Warsaw41075852001-09-23 03:18:13 +0000357
358 def test_encode_7bit(self):
359 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000360 msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000361 eq(msg.get_payload(), 'hello world\n')
362 eq(msg['content-transfer-encoding'], '7bit')
Barry Warsaw65279d02001-09-26 05:47:08 +0000363 msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000364 eq(msg.get_payload(), 'hello \x7f world\n')
365 eq(msg['content-transfer-encoding'], '7bit')
366
367 def test_encode_8bit(self):
368 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000369 msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000370 eq(msg.get_payload(), 'hello \x80 world\n')
371 eq(msg['content-transfer-encoding'], '8bit')
372
Barry Warsaw409a4c02002-04-10 21:01:31 +0000373 def test_encode_empty_payload(self):
374 eq = self.assertEqual
375 msg = Message()
376 msg.set_charset('us-ascii')
377 eq(msg['content-transfer-encoding'], '7bit')
378
Barry Warsaw41075852001-09-23 03:18:13 +0000379 def test_encode_base64(self):
380 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000381 msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
Barry Warsaw41075852001-09-23 03:18:13 +0000382 eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
383 eq(msg['content-transfer-encoding'], 'base64')
384
385 def test_encode_quoted_printable(self):
386 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000387 msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
Barry Warsaw41075852001-09-23 03:18:13 +0000388 eq(msg.get_payload(), 'hello=20world\n')
389 eq(msg['content-transfer-encoding'], 'quoted-printable')
390
Barry Warsaw409a4c02002-04-10 21:01:31 +0000391 def test_default_cte(self):
392 eq = self.assertEqual
393 msg = MIMEText('hello world')
394 eq(msg['content-transfer-encoding'], '7bit')
395
396 def test_default_cte(self):
397 eq = self.assertEqual
398 # With no explicit _charset its us-ascii, and all are 7-bit
399 msg = MIMEText('hello world')
400 eq(msg['content-transfer-encoding'], '7bit')
401 # Similar, but with 8-bit data
402 msg = MIMEText('hello \xf8 world')
403 eq(msg['content-transfer-encoding'], '8bit')
404 # And now with a different charset
405 msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
406 eq(msg['content-transfer-encoding'], 'quoted-printable')
407
Barry Warsaw41075852001-09-23 03:18:13 +0000408
Barry Warsaw08a534d2001-10-04 17:58:50 +0000409
410# Test long header wrapping
Barry Warsawb6a92132002-06-28 23:49:33 +0000411class TestLongHeaders(TestEmailBase):
412 def test_split_long_continuation(self):
413 eq = self.ndiffAssertEqual
414 msg = email.message_from_string("""\
415Subject: bug demonstration
416\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
417\tmore text
418
419test
420""")
421 sfp = StringIO()
422 g = Generator(sfp)
423 g.flatten(msg)
424 eq(sfp.getvalue(), """\
425Subject: bug demonstration
426\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
427\tmore text
428
429test
430""")
431
432 def test_another_long_almost_unsplittable_header(self):
433 eq = self.ndiffAssertEqual
434 hstr = """\
435bug demonstration
436\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
437\tmore text"""
438 h = Header(hstr, continuation_ws='\t')
439 eq(h.encode(), """\
440bug demonstration
441\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
442\tmore text""")
443 h = Header(hstr)
444 eq(h.encode(), """\
445bug demonstration
446 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
447 more text""")
448
449 def test_long_nonstring(self):
450 eq = self.ndiffAssertEqual
451 g = Charset("iso-8859-1")
452 cz = Charset("iso-8859-2")
453 utf8 = Charset("utf-8")
454 g_head = "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
455 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
456 utf8_head = u"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
457 h = Header(g_head, g)
458 h.append(cz_head, cz)
459 h.append(utf8_head, utf8)
460 msg = Message()
461 msg['Subject'] = h
462 sfp = StringIO()
463 g = Generator(sfp)
464 g.flatten(msg)
465 eq(sfp.getvalue(), '''\
466Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
467 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
468 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
469 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
470 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
471 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
472 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
473 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
474 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
475 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
476 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
477 =?utf-8?b?cyBOdW5zdHVjayBnaXQgdW5k?=
478 =?utf-8?b?IFNsb3Rlcm1leWVyPyBKYSEgQmVpaGVyaHVuZCBkYXMgT2Rl?=
479 =?utf-8?b?ciBkaWUgRmxpcHBlcndhbGR0?=
480 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=
481
482''')
483 eq(h.encode(), '''\
484=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
485 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
486 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
487 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
488 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
489 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
490 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
491 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
492 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
493 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
494 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
495 =?utf-8?b?cyBOdW5zdHVjayBnaXQgdW5k?=
496 =?utf-8?b?IFNsb3Rlcm1leWVyPyBKYSEgQmVpaGVyaHVuZCBkYXMgT2Rl?=
497 =?utf-8?b?ciBkaWUgRmxpcHBlcndhbGR0?=
498 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=''')
499
500 def test_long_header_encode(self):
501 eq = self.ndiffAssertEqual
502 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
503 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
504 header_name='X-Foobar-Spoink-Defrobnit')
505 eq(h.encode(), '''\
506wasnipoop; giraffes="very-long-necked-animals";
507 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
508
509 def test_long_header_encode_with_tab_continuation(self):
510 eq = self.ndiffAssertEqual
511 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
512 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
513 header_name='X-Foobar-Spoink-Defrobnit',
514 continuation_ws='\t')
515 eq(h.encode(), '''\
516wasnipoop; giraffes="very-long-necked-animals";
517\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
518
Barry Warsaw41075852001-09-23 03:18:13 +0000519 def test_header_splitter(self):
Barry Warsawb6a92132002-06-28 23:49:33 +0000520 eq = self.ndiffAssertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000521 msg = MIMEText('')
Barry Warsaw41075852001-09-23 03:18:13 +0000522 # It'd be great if we could use add_header() here, but that doesn't
523 # guarantee an order of the parameters.
524 msg['X-Foobar-Spoink-Defrobnit'] = (
525 'wasnipoop; giraffes="very-long-necked-animals"; '
526 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
527 sfp = StringIO()
528 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +0000529 g.flatten(msg)
Barry Warsawb6a92132002-06-28 23:49:33 +0000530 eq(sfp.getvalue(), '''\
Barry Warsaw409a4c02002-04-10 21:01:31 +0000531Content-Type: text/plain; charset="us-ascii"
532MIME-Version: 1.0
533Content-Transfer-Encoding: 7bit
534X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
Barry Warsaw16f90552002-04-16 05:06:42 +0000535\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
Barry Warsaw409a4c02002-04-10 21:01:31 +0000536
537''')
Barry Warsaw41075852001-09-23 03:18:13 +0000538
Barry Warsaw07227d12001-10-17 20:52:26 +0000539 def test_no_semis_header_splitter(self):
Barry Warsawb6a92132002-06-28 23:49:33 +0000540 eq = self.ndiffAssertEqual
Barry Warsaw07227d12001-10-17 20:52:26 +0000541 msg = Message()
542 msg['From'] = 'test@dom.ain'
Barry Warsawb6a92132002-06-28 23:49:33 +0000543 msg['References'] = SPACE.join(['<%d@dom.ain>' % i for i in range(10)])
Barry Warsaw07227d12001-10-17 20:52:26 +0000544 msg.set_payload('Test')
545 sfp = StringIO()
546 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +0000547 g.flatten(msg)
Barry Warsawb6a92132002-06-28 23:49:33 +0000548 eq(sfp.getvalue(), """\
Barry Warsaw07227d12001-10-17 20:52:26 +0000549From: test@dom.ain
550References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
Tim Peterse0c446b2001-10-18 21:57:37 +0000551\t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
Barry Warsaw07227d12001-10-17 20:52:26 +0000552
553Test""")
554
555 def test_no_split_long_header(self):
Barry Warsawb6a92132002-06-28 23:49:33 +0000556 eq = self.ndiffAssertEqual
557 hstr = 'References: ' + 'x' * 80
558 h = Header(hstr, continuation_ws='\t')
559 eq(h.encode(), """\
560References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
Barry Warsaw07227d12001-10-17 20:52:26 +0000561
Barry Warsaw409a4c02002-04-10 21:01:31 +0000562 def test_splitting_multiple_long_lines(self):
Barry Warsawb6a92132002-06-28 23:49:33 +0000563 eq = self.ndiffAssertEqual
564 hstr = """\
Barry Warsaw409a4c02002-04-10 21:01:31 +0000565from babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
Barry Warsaw16f90552002-04-16 05:06:42 +0000566\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
567\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
Barry Warsaw409a4c02002-04-10 21:01:31 +0000568"""
Barry Warsawb6a92132002-06-28 23:49:33 +0000569 h = Header(hstr, continuation_ws='\t')
570 eq(h.encode(), """\
571from babylon.socal-raves.org (localhost [127.0.0.1]);
Barry Warsaw16f90552002-04-16 05:06:42 +0000572\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
573\tfor <mailman-admin@babylon.socal-raves.org>;
574\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
575\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
576\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
577\tfor <mailman-admin@babylon.socal-raves.org>;
578\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
579\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
580\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
581\tfor <mailman-admin@babylon.socal-raves.org>;
Barry Warsawb6a92132002-06-28 23:49:33 +0000582\tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
Barry Warsaw409a4c02002-04-10 21:01:31 +0000583
Barry Warsaw41075852001-09-23 03:18:13 +0000584
Barry Warsaw08a534d2001-10-04 17:58:50 +0000585
586# Test mangling of "From " lines in the body of a message
Barry Warsaw41075852001-09-23 03:18:13 +0000587class TestFromMangling(unittest.TestCase):
588 def setUp(self):
589 self.msg = Message()
590 self.msg['From'] = 'aaa@bbb.org'
Barry Warsaw2c685062002-06-02 19:09:27 +0000591 self.msg.set_payload("""\
Barry Warsaw41075852001-09-23 03:18:13 +0000592From the desk of A.A.A.:
593Blah blah blah
594""")
595
596 def test_mangled_from(self):
597 s = StringIO()
598 g = Generator(s, mangle_from_=1)
Barry Warsaw2c685062002-06-02 19:09:27 +0000599 g.flatten(self.msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000600 self.assertEqual(s.getvalue(), """\
601From: aaa@bbb.org
602
603>From the desk of A.A.A.:
604Blah blah blah
605""")
606
607 def test_dont_mangle_from(self):
608 s = StringIO()
609 g = Generator(s, mangle_from_=0)
Barry Warsaw2c685062002-06-02 19:09:27 +0000610 g.flatten(self.msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000611 self.assertEqual(s.getvalue(), """\
612From: aaa@bbb.org
613
614From the desk of A.A.A.:
615Blah blah blah
616""")
617
618
Barry Warsaw08a534d2001-10-04 17:58:50 +0000619
Barry Warsawfee435a2001-10-09 19:23:57 +0000620# Test the basic MIMEAudio class
621class TestMIMEAudio(unittest.TestCase):
622 def setUp(self):
623 # In Python, audiotest.au lives in Lib/test not Lib/test/data
624 fp = open(findfile('audiotest.au'))
625 try:
626 self._audiodata = fp.read()
627 finally:
628 fp.close()
629 self._au = MIMEAudio(self._audiodata)
630
631 def test_guess_minor_type(self):
632 self.assertEqual(self._au.get_type(), 'audio/basic')
633
634 def test_encoding(self):
635 payload = self._au.get_payload()
636 self.assertEqual(base64.decodestring(payload), self._audiodata)
637
638 def checkSetMinor(self):
639 au = MIMEAudio(self._audiodata, 'fish')
640 self.assertEqual(im.get_type(), 'audio/fish')
641
642 def test_custom_encoder(self):
643 eq = self.assertEqual
644 def encoder(msg):
645 orig = msg.get_payload()
646 msg.set_payload(0)
647 msg['Content-Transfer-Encoding'] = 'broken64'
648 au = MIMEAudio(self._audiodata, _encoder=encoder)
649 eq(au.get_payload(), 0)
650 eq(au['content-transfer-encoding'], 'broken64')
651
652 def test_add_header(self):
653 eq = self.assertEqual
654 unless = self.failUnless
655 self._au.add_header('Content-Disposition', 'attachment',
656 filename='audiotest.au')
657 eq(self._au['content-disposition'],
658 'attachment; filename="audiotest.au"')
659 eq(self._au.get_params(header='content-disposition'),
660 [('attachment', ''), ('filename', 'audiotest.au')])
661 eq(self._au.get_param('filename', header='content-disposition'),
662 'audiotest.au')
663 missing = []
664 eq(self._au.get_param('attachment', header='content-disposition'), '')
665 unless(self._au.get_param('foo', failobj=missing,
666 header='content-disposition') is missing)
667 # Try some missing stuff
668 unless(self._au.get_param('foobar', missing) is missing)
669 unless(self._au.get_param('attachment', missing,
670 header='foobar') is missing)
671
672
673
Barry Warsaw65279d02001-09-26 05:47:08 +0000674# Test the basic MIMEImage class
675class TestMIMEImage(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000676 def setUp(self):
677 fp = openfile('PyBanner048.gif')
678 try:
679 self._imgdata = fp.read()
680 finally:
681 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000682 self._im = MIMEImage(self._imgdata)
Barry Warsaw41075852001-09-23 03:18:13 +0000683
684 def test_guess_minor_type(self):
685 self.assertEqual(self._im.get_type(), 'image/gif')
686
687 def test_encoding(self):
688 payload = self._im.get_payload()
689 self.assertEqual(base64.decodestring(payload), self._imgdata)
690
691 def checkSetMinor(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000692 im = MIMEImage(self._imgdata, 'fish')
Barry Warsaw41075852001-09-23 03:18:13 +0000693 self.assertEqual(im.get_type(), 'image/fish')
694
695 def test_custom_encoder(self):
696 eq = self.assertEqual
697 def encoder(msg):
698 orig = msg.get_payload()
699 msg.set_payload(0)
700 msg['Content-Transfer-Encoding'] = 'broken64'
Barry Warsaw65279d02001-09-26 05:47:08 +0000701 im = MIMEImage(self._imgdata, _encoder=encoder)
Barry Warsaw41075852001-09-23 03:18:13 +0000702 eq(im.get_payload(), 0)
703 eq(im['content-transfer-encoding'], 'broken64')
704
705 def test_add_header(self):
706 eq = self.assertEqual
707 unless = self.failUnless
708 self._im.add_header('Content-Disposition', 'attachment',
709 filename='dingusfish.gif')
710 eq(self._im['content-disposition'],
711 'attachment; filename="dingusfish.gif"')
712 eq(self._im.get_params(header='content-disposition'),
Barry Warsaw65279d02001-09-26 05:47:08 +0000713 [('attachment', ''), ('filename', 'dingusfish.gif')])
Barry Warsaw41075852001-09-23 03:18:13 +0000714 eq(self._im.get_param('filename', header='content-disposition'),
715 'dingusfish.gif')
716 missing = []
Barry Warsaw65279d02001-09-26 05:47:08 +0000717 eq(self._im.get_param('attachment', header='content-disposition'), '')
718 unless(self._im.get_param('foo', failobj=missing,
Barry Warsaw41075852001-09-23 03:18:13 +0000719 header='content-disposition') is missing)
720 # Try some missing stuff
721 unless(self._im.get_param('foobar', missing) is missing)
722 unless(self._im.get_param('attachment', missing,
723 header='foobar') is missing)
724
725
Barry Warsaw08a534d2001-10-04 17:58:50 +0000726
Barry Warsaw65279d02001-09-26 05:47:08 +0000727# Test the basic MIMEText class
728class TestMIMEText(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000729 def setUp(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000730 self._msg = MIMEText('hello there')
Barry Warsaw41075852001-09-23 03:18:13 +0000731
732 def test_types(self):
733 eq = self.assertEqual
734 unless = self.failUnless
735 eq(self._msg.get_type(), 'text/plain')
736 eq(self._msg.get_param('charset'), 'us-ascii')
737 missing = []
738 unless(self._msg.get_param('foobar', missing) is missing)
739 unless(self._msg.get_param('charset', missing, header='foobar')
740 is missing)
741
742 def test_payload(self):
743 self.assertEqual(self._msg.get_payload(), 'hello there\n')
744 self.failUnless(not self._msg.is_multipart())
745
Barry Warsaw409a4c02002-04-10 21:01:31 +0000746 def test_charset(self):
747 eq = self.assertEqual
748 msg = MIMEText('hello there', _charset='us-ascii')
749 eq(msg.get_charset().input_charset, 'us-ascii')
750 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
751
Barry Warsaw41075852001-09-23 03:18:13 +0000752
Barry Warsaw08a534d2001-10-04 17:58:50 +0000753
754# Test a more complicated multipart/mixed type message
Barry Warsaw41075852001-09-23 03:18:13 +0000755class TestMultipartMixed(unittest.TestCase):
756 def setUp(self):
757 fp = openfile('PyBanner048.gif')
758 try:
759 data = fp.read()
760 finally:
761 fp.close()
762
763 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
Barry Warsaw65279d02001-09-26 05:47:08 +0000764 image = MIMEImage(data, name='dingusfish.gif')
Barry Warsaw41075852001-09-23 03:18:13 +0000765 image.add_header('content-disposition', 'attachment',
766 filename='dingusfish.gif')
Barry Warsaw65279d02001-09-26 05:47:08 +0000767 intro = MIMEText('''\
Barry Warsaw41075852001-09-23 03:18:13 +0000768Hi there,
769
770This is the dingus fish.
771''')
Barry Warsaw2c685062002-06-02 19:09:27 +0000772 container.attach(intro)
773 container.attach(image)
Barry Warsaw41075852001-09-23 03:18:13 +0000774 container['From'] = 'Barry <barry@digicool.com>'
775 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
776 container['Subject'] = 'Here is your dingus fish'
Tim Peters527e64f2001-10-04 05:36:56 +0000777
Barry Warsaw41075852001-09-23 03:18:13 +0000778 now = 987809702.54848599
779 timetuple = time.localtime(now)
780 if timetuple[-1] == 0:
781 tzsecs = time.timezone
782 else:
783 tzsecs = time.altzone
784 if tzsecs > 0:
785 sign = '-'
786 else:
787 sign = '+'
788 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
789 container['Date'] = time.strftime(
790 '%a, %d %b %Y %H:%M:%S',
791 time.localtime(now)) + tzoffset
792 self._msg = container
793 self._im = image
794 self._txt = intro
795
796 def test_hierarchy(self):
797 # convenience
798 eq = self.assertEqual
799 unless = self.failUnless
800 raises = self.assertRaises
801 # tests
802 m = self._msg
803 unless(m.is_multipart())
804 eq(m.get_type(), 'multipart/mixed')
805 eq(len(m.get_payload()), 2)
806 raises(IndexError, m.get_payload, 2)
807 m0 = m.get_payload(0)
808 m1 = m.get_payload(1)
809 unless(m0 is self._txt)
810 unless(m1 is self._im)
811 eq(m.get_payload(), [m0, m1])
812 unless(not m0.is_multipart())
813 unless(not m1.is_multipart())
814
Barry Warsaw409a4c02002-04-10 21:01:31 +0000815 def test_no_parts_in_a_multipart(self):
816 outer = MIMEBase('multipart', 'mixed')
817 outer['Subject'] = 'A subject'
818 outer['To'] = 'aperson@dom.ain'
819 outer['From'] = 'bperson@dom.ain'
820 outer.preamble = ''
821 outer.epilogue = ''
822 outer.set_boundary('BOUNDARY')
823 msg = MIMEText('hello world')
824 self.assertEqual(outer.as_string(), '''\
825Content-Type: multipart/mixed; boundary="BOUNDARY"
826MIME-Version: 1.0
827Subject: A subject
828To: aperson@dom.ain
829From: bperson@dom.ain
830
831--BOUNDARY
832
833
834--BOUNDARY--
Barry Warsaw16f90552002-04-16 05:06:42 +0000835''')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000836
837 def test_one_part_in_a_multipart(self):
838 outer = MIMEBase('multipart', 'mixed')
839 outer['Subject'] = 'A subject'
840 outer['To'] = 'aperson@dom.ain'
841 outer['From'] = 'bperson@dom.ain'
842 outer.preamble = ''
843 outer.epilogue = ''
844 outer.set_boundary('BOUNDARY')
845 msg = MIMEText('hello world')
846 outer.attach(msg)
847 self.assertEqual(outer.as_string(), '''\
848Content-Type: multipart/mixed; boundary="BOUNDARY"
849MIME-Version: 1.0
850Subject: A subject
851To: aperson@dom.ain
852From: bperson@dom.ain
853
854--BOUNDARY
855Content-Type: text/plain; charset="us-ascii"
856MIME-Version: 1.0
857Content-Transfer-Encoding: 7bit
858
859hello world
860
861--BOUNDARY--
Barry Warsaw16f90552002-04-16 05:06:42 +0000862''')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000863
864 def test_seq_parts_in_a_multipart(self):
865 outer = MIMEBase('multipart', 'mixed')
866 outer['Subject'] = 'A subject'
867 outer['To'] = 'aperson@dom.ain'
868 outer['From'] = 'bperson@dom.ain'
869 outer.preamble = ''
870 outer.epilogue = ''
871 msg = MIMEText('hello world')
872 outer.attach(msg)
873 outer.set_boundary('BOUNDARY')
874 self.assertEqual(outer.as_string(), '''\
875Content-Type: multipart/mixed; boundary="BOUNDARY"
876MIME-Version: 1.0
877Subject: A subject
878To: aperson@dom.ain
879From: bperson@dom.ain
880
881--BOUNDARY
882Content-Type: text/plain; charset="us-ascii"
883MIME-Version: 1.0
884Content-Transfer-Encoding: 7bit
885
886hello world
887
888--BOUNDARY--
Barry Warsaw16f90552002-04-16 05:06:42 +0000889''')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000890
Barry Warsaw41075852001-09-23 03:18:13 +0000891
Barry Warsaw08a534d2001-10-04 17:58:50 +0000892
893# Test some badly formatted messages
Barry Warsaw41075852001-09-23 03:18:13 +0000894class TestNonConformant(TestEmailBase):
895 def test_parse_missing_minor_type(self):
896 eq = self.assertEqual
897 msg = self._msgobj('msg_14.txt')
898 eq(msg.get_type(), 'text')
899 eq(msg.get_main_type(), 'text')
900 self.failUnless(msg.get_subtype() is None)
901
902 def test_bogus_boundary(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +0000903 fp = openfile(findfile('msg_15.txt'))
Barry Warsaw41075852001-09-23 03:18:13 +0000904 try:
905 data = fp.read()
906 finally:
907 fp.close()
908 p = Parser()
909 # Note, under a future non-strict parsing mode, this would parse the
910 # message into the intended message tree.
911 self.assertRaises(Errors.BoundaryError, p.parsestr, data)
912
Barry Warsaw409a4c02002-04-10 21:01:31 +0000913 def test_multipart_no_boundary(self):
914 fp = openfile(findfile('msg_25.txt'))
Barry Warsaw329d3af2002-07-09 02:38:24 +0000915 try:
916 self.assertRaises(Errors.BoundaryError,
917 email.message_from_file, fp)
918 finally:
919 fp.close()
Barry Warsaw409a4c02002-04-10 21:01:31 +0000920
Barry Warsaw41075852001-09-23 03:18:13 +0000921
Barry Warsaw08a534d2001-10-04 17:58:50 +0000922
923# Test RFC 2047 header encoding and decoding
Barry Warsaw41075852001-09-23 03:18:13 +0000924class TestRFC2047(unittest.TestCase):
925 def test_iso_8859_1(self):
926 eq = self.assertEqual
927 s = '=?iso-8859-1?q?this=20is=20some=20text?='
928 eq(Utils.decode(s), 'this is some text')
929 s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
Barry Warsaw409a4c02002-04-10 21:01:31 +0000930 eq(Utils.decode(s), u'Keld J\xf8rn Simonsen')
Barry Warsaw41075852001-09-23 03:18:13 +0000931 s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
932 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
933 eq(Utils.decode(s), 'If you can read this you understand the example.')
934 s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
935 eq(Utils.decode(s),
936 u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
937 s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
Barry Warsaw409a4c02002-04-10 21:01:31 +0000938 eq(Utils.decode(s), u'this issome text')
939 s = '=?iso-8859-1?q?this=20is_?= =?iso-8859-1?q?some=20text?='
Barry Warsaw41075852001-09-23 03:18:13 +0000940 eq(Utils.decode(s), u'this is some text')
941
942 def test_encode_header(self):
943 eq = self.assertEqual
944 s = 'this is some text'
945 eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
946 s = 'Keld_J\xf8rn_Simonsen'
947 eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
948 s1 = 'If you can read this yo'
949 s2 = 'u understand the example.'
950 eq(Utils.encode(s1, encoding='b'),
951 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
952 eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
953 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
954
955
Barry Warsaw08a534d2001-10-04 17:58:50 +0000956
957# Test the MIMEMessage class
Barry Warsaw65279d02001-09-26 05:47:08 +0000958class TestMIMEMessage(TestEmailBase):
Barry Warsaw41075852001-09-23 03:18:13 +0000959 def setUp(self):
960 fp = openfile('msg_11.txt')
Barry Warsaw329d3af2002-07-09 02:38:24 +0000961 try:
962 self._text = fp.read()
963 finally:
964 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +0000965
966 def test_type_error(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000967 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
Barry Warsaw41075852001-09-23 03:18:13 +0000968
969 def test_valid_argument(self):
970 eq = self.assertEqual
Barry Warsaw2c685062002-06-02 19:09:27 +0000971 unless = self.failUnless
Barry Warsaw41075852001-09-23 03:18:13 +0000972 subject = 'A sub-message'
973 m = Message()
974 m['Subject'] = subject
Barry Warsaw65279d02001-09-26 05:47:08 +0000975 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000976 eq(r.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +0000977 payload = r.get_payload()
978 unless(type(payload), ListType)
979 eq(len(payload), 1)
980 subpart = payload[0]
981 unless(subpart is m)
982 eq(subpart['subject'], subject)
983
984 def test_bad_multipart(self):
985 eq = self.assertEqual
986 msg1 = Message()
987 msg1['Subject'] = 'subpart 1'
988 msg2 = Message()
989 msg2['Subject'] = 'subpart 2'
990 r = MIMEMessage(msg1)
991 self.assertRaises(Errors.MultipartConversionError, r.attach, msg2)
Barry Warsaw41075852001-09-23 03:18:13 +0000992
993 def test_generate(self):
994 # First craft the message to be encapsulated
995 m = Message()
996 m['Subject'] = 'An enclosed message'
Barry Warsaw2c685062002-06-02 19:09:27 +0000997 m.set_payload('Here is the body of the message.\n')
Barry Warsaw65279d02001-09-26 05:47:08 +0000998 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000999 r['Subject'] = 'The enclosing message'
1000 s = StringIO()
1001 g = Generator(s)
Barry Warsaw2c685062002-06-02 19:09:27 +00001002 g.flatten(r)
Barry Warsaw65279d02001-09-26 05:47:08 +00001003 self.assertEqual(s.getvalue(), """\
1004Content-Type: message/rfc822
1005MIME-Version: 1.0
1006Subject: The enclosing message
1007
1008Subject: An enclosed message
1009
1010Here is the body of the message.
1011""")
1012
1013 def test_parse_message_rfc822(self):
1014 eq = self.assertEqual
Barry Warsaw2c685062002-06-02 19:09:27 +00001015 unless = self.failUnless
Barry Warsaw65279d02001-09-26 05:47:08 +00001016 msg = self._msgobj('msg_11.txt')
1017 eq(msg.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +00001018 payload = msg.get_payload()
1019 unless(isinstance(payload, ListType))
1020 eq(len(payload), 1)
1021 submsg = payload[0]
Barry Warsaw65279d02001-09-26 05:47:08 +00001022 self.failUnless(isinstance(submsg, Message))
1023 eq(submsg['subject'], 'An enclosed message')
1024 eq(submsg.get_payload(), 'Here is the body of the message.\n')
1025
1026 def test_dsn(self):
1027 eq = self.assertEqual
1028 unless = self.failUnless
1029 # msg 16 is a Delivery Status Notification, see RFC XXXX
1030 msg = self._msgobj('msg_16.txt')
1031 eq(msg.get_type(), 'multipart/report')
1032 unless(msg.is_multipart())
1033 eq(len(msg.get_payload()), 3)
1034 # Subpart 1 is a text/plain, human readable section
1035 subpart = msg.get_payload(0)
1036 eq(subpart.get_type(), 'text/plain')
1037 eq(subpart.get_payload(), """\
1038This report relates to a message you sent with the following header fields:
1039
1040 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1041 Date: Sun, 23 Sep 2001 20:10:55 -0700
1042 From: "Ian T. Henry" <henryi@oxy.edu>
1043 To: SoCal Raves <scr@socal-raves.org>
1044 Subject: [scr] yeah for Ians!!
1045
1046Your message cannot be delivered to the following recipients:
1047
1048 Recipient address: jangel1@cougar.noc.ucla.edu
1049 Reason: recipient reached disk quota
1050
1051""")
1052 # Subpart 2 contains the machine parsable DSN information. It
1053 # consists of two blocks of headers, represented by two nested Message
1054 # objects.
1055 subpart = msg.get_payload(1)
1056 eq(subpart.get_type(), 'message/delivery-status')
1057 eq(len(subpart.get_payload()), 2)
1058 # message/delivery-status should treat each block as a bunch of
1059 # headers, i.e. a bunch of Message objects.
1060 dsn1 = subpart.get_payload(0)
1061 unless(isinstance(dsn1, Message))
1062 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1063 eq(dsn1.get_param('dns', header='reporting-mta'), '')
1064 # Try a missing one <wink>
1065 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
1066 dsn2 = subpart.get_payload(1)
1067 unless(isinstance(dsn2, Message))
1068 eq(dsn2['action'], 'failed')
1069 eq(dsn2.get_params(header='original-recipient'),
1070 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1071 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
1072 # Subpart 3 is the original message
1073 subpart = msg.get_payload(2)
1074 eq(subpart.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +00001075 payload = subpart.get_payload()
1076 unless(isinstance(payload, ListType))
1077 eq(len(payload), 1)
1078 subsubpart = payload[0]
Barry Warsaw65279d02001-09-26 05:47:08 +00001079 unless(isinstance(subsubpart, Message))
1080 eq(subsubpart.get_type(), 'text/plain')
1081 eq(subsubpart['message-id'],
1082 '<002001c144a6$8752e060$56104586@oxy.edu>')
Barry Warsaw41075852001-09-23 03:18:13 +00001083
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001084 def test_epilogue(self):
1085 fp = openfile('msg_21.txt')
1086 try:
1087 text = fp.read()
1088 finally:
1089 fp.close()
1090 msg = Message()
1091 msg['From'] = 'aperson@dom.ain'
1092 msg['To'] = 'bperson@dom.ain'
1093 msg['Subject'] = 'Test'
1094 msg.preamble = 'MIME message\n'
1095 msg.epilogue = 'End of MIME message\n'
1096 msg1 = MIMEText('One')
1097 msg2 = MIMEText('Two')
1098 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
Barry Warsaw2c685062002-06-02 19:09:27 +00001099 msg.attach(msg1)
1100 msg.attach(msg2)
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001101 sfp = StringIO()
1102 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +00001103 g.flatten(msg)
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001104 self.assertEqual(sfp.getvalue(), text)
1105
Barry Warsaw329d3af2002-07-09 02:38:24 +00001106 def test_default_type(self):
1107 eq = self.assertEqual
1108 fp = openfile('msg_30.txt')
1109 try:
1110 msg = email.message_from_file(fp)
1111 finally:
1112 fp.close()
1113 container1 = msg.get_payload(0)
1114 eq(container1.get_default_type(), 'message/rfc822')
1115 eq(container1.get_type(), None)
1116 container2 = msg.get_payload(1)
1117 eq(container2.get_default_type(), 'message/rfc822')
1118 eq(container2.get_type(), None)
1119 container1a = container1.get_payload(0)
1120 eq(container1a.get_default_type(), 'text/plain')
1121 eq(container1a.get_type(), 'text/plain')
1122 container2a = container2.get_payload(0)
1123 eq(container2a.get_default_type(), 'text/plain')
1124 eq(container2a.get_type(), 'text/plain')
1125
1126 def test_default_type_with_explicit_container_type(self):
1127 eq = self.assertEqual
1128 fp = openfile('msg_28.txt')
1129 try:
1130 msg = email.message_from_file(fp)
1131 finally:
1132 fp.close()
1133 container1 = msg.get_payload(0)
1134 eq(container1.get_default_type(), 'message/rfc822')
1135 eq(container1.get_type(), 'message/rfc822')
1136 container2 = msg.get_payload(1)
1137 eq(container2.get_default_type(), 'message/rfc822')
1138 eq(container2.get_type(), 'message/rfc822')
1139 container1a = container1.get_payload(0)
1140 eq(container1a.get_default_type(), 'text/plain')
1141 eq(container1a.get_type(), 'text/plain')
1142 container2a = container2.get_payload(0)
1143 eq(container2a.get_default_type(), 'text/plain')
1144 eq(container2a.get_type(), 'text/plain')
1145
1146 def test_default_type_non_parsed(self):
1147 eq = self.assertEqual
1148 neq = self.ndiffAssertEqual
1149 # Set up container
1150 container = MIMEMultipart('digest', 'BOUNDARY')
1151 container.epilogue = '\n'
1152 # Set up subparts
1153 subpart1a = MIMEText('message 1\n')
1154 subpart2a = MIMEText('message 2\n')
1155 subpart1 = MIMEMessage(subpart1a)
1156 subpart2 = MIMEMessage(subpart2a)
1157 container.attach(subpart1)
1158 container.attach(subpart2)
1159 eq(subpart1.get_type(), 'message/rfc822')
1160 eq(subpart1.get_default_type(), 'message/rfc822')
1161 eq(subpart2.get_type(), 'message/rfc822')
1162 eq(subpart2.get_default_type(), 'message/rfc822')
1163 neq(container.as_string(0), '''\
1164Content-Type: multipart/digest; boundary="BOUNDARY"
1165MIME-Version: 1.0
1166
1167--BOUNDARY
1168Content-Type: message/rfc822
1169MIME-Version: 1.0
1170
1171Content-Type: text/plain; charset="us-ascii"
1172MIME-Version: 1.0
1173Content-Transfer-Encoding: 7bit
1174
1175message 1
1176
1177--BOUNDARY
1178Content-Type: message/rfc822
1179MIME-Version: 1.0
1180
1181Content-Type: text/plain; charset="us-ascii"
1182MIME-Version: 1.0
1183Content-Transfer-Encoding: 7bit
1184
1185message 2
1186
1187--BOUNDARY--
1188''')
1189 del subpart1['content-type']
1190 del subpart1['mime-version']
1191 del subpart2['content-type']
1192 del subpart2['mime-version']
1193 eq(subpart1.get_type(), None)
1194 eq(subpart1.get_default_type(), 'message/rfc822')
1195 eq(subpart2.get_type(), None)
1196 eq(subpart2.get_default_type(), 'message/rfc822')
1197 neq(container.as_string(0), '''\
1198Content-Type: multipart/digest; boundary="BOUNDARY"
1199MIME-Version: 1.0
1200
1201--BOUNDARY
1202
1203Content-Type: text/plain; charset="us-ascii"
1204MIME-Version: 1.0
1205Content-Transfer-Encoding: 7bit
1206
1207message 1
1208
1209--BOUNDARY
1210
1211Content-Type: text/plain; charset="us-ascii"
1212MIME-Version: 1.0
1213Content-Transfer-Encoding: 7bit
1214
1215message 2
1216
1217--BOUNDARY--
1218''')
1219
Barry Warsaw41075852001-09-23 03:18:13 +00001220
Barry Warsaw08a534d2001-10-04 17:58:50 +00001221
1222# A general test of parser->model->generator idempotency. IOW, read a message
1223# in, parse it into a message object tree, then without touching the tree,
1224# regenerate the plain text. The original text and the transformed text
1225# should be identical. Note: that we ignore the Unix-From since that may
1226# contain a changed date.
Barry Warsawb6a92132002-06-28 23:49:33 +00001227class TestIdempotent(TestEmailBase):
Barry Warsaw41075852001-09-23 03:18:13 +00001228 def _msgobj(self, filename):
1229 fp = openfile(filename)
1230 try:
1231 data = fp.read()
1232 finally:
1233 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +00001234 msg = email.message_from_string(data)
1235 return msg, data
Barry Warsaw41075852001-09-23 03:18:13 +00001236
1237 def _idempotent(self, msg, text):
Barry Warsawb6a92132002-06-28 23:49:33 +00001238 eq = self.ndiffAssertEqual
Barry Warsaw41075852001-09-23 03:18:13 +00001239 s = StringIO()
1240 g = Generator(s, maxheaderlen=0)
Barry Warsaw2c685062002-06-02 19:09:27 +00001241 g.flatten(msg)
Barry Warsaw41075852001-09-23 03:18:13 +00001242 eq(text, s.getvalue())
1243
1244 def test_parse_text_message(self):
1245 eq = self.assertEquals
1246 msg, text = self._msgobj('msg_01.txt')
1247 eq(msg.get_type(), 'text/plain')
1248 eq(msg.get_main_type(), 'text')
1249 eq(msg.get_subtype(), 'plain')
Barry Warsaw65279d02001-09-26 05:47:08 +00001250 eq(msg.get_params()[1], ('charset', 'us-ascii'))
Barry Warsaw41075852001-09-23 03:18:13 +00001251 eq(msg.get_param('charset'), 'us-ascii')
1252 eq(msg.preamble, None)
1253 eq(msg.epilogue, None)
1254 self._idempotent(msg, text)
1255
1256 def test_parse_untyped_message(self):
1257 eq = self.assertEquals
1258 msg, text = self._msgobj('msg_03.txt')
1259 eq(msg.get_type(), None)
1260 eq(msg.get_params(), None)
1261 eq(msg.get_param('charset'), None)
1262 self._idempotent(msg, text)
1263
1264 def test_simple_multipart(self):
1265 msg, text = self._msgobj('msg_04.txt')
1266 self._idempotent(msg, text)
1267
1268 def test_MIME_digest(self):
1269 msg, text = self._msgobj('msg_02.txt')
1270 self._idempotent(msg, text)
1271
Barry Warsaw19698172002-06-29 15:23:39 +00001272 def test_long_header(self):
1273 msg, text = self._msgobj('msg_27.txt')
1274 self._idempotent(msg, text)
1275
Barry Warsaw329d3af2002-07-09 02:38:24 +00001276 def test_MIME_digest_with_part_headers(self):
1277 msg, text = self._msgobj('msg_28.txt')
1278 self._idempotent(msg, text)
Barry Warsawb6a92132002-06-28 23:49:33 +00001279
Barry Warsaw41075852001-09-23 03:18:13 +00001280 def test_mixed_with_image(self):
1281 msg, text = self._msgobj('msg_06.txt')
1282 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +00001283
Barry Warsaw41075852001-09-23 03:18:13 +00001284 def test_multipart_report(self):
1285 msg, text = self._msgobj('msg_05.txt')
1286 self._idempotent(msg, text)
Barry Warsaw65279d02001-09-26 05:47:08 +00001287
1288 def test_dsn(self):
1289 msg, text = self._msgobj('msg_16.txt')
1290 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +00001291
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001292 def test_preamble_epilogue(self):
1293 msg, text = self._msgobj('msg_21.txt')
1294 self._idempotent(msg, text)
1295
Barry Warsaw763af412002-01-27 06:48:47 +00001296 def test_multipart_one_part(self):
1297 msg, text = self._msgobj('msg_23.txt')
1298 self._idempotent(msg, text)
1299
Barry Warsaw409a4c02002-04-10 21:01:31 +00001300 def test_multipart_no_parts(self):
1301 msg, text = self._msgobj('msg_24.txt')
1302 self._idempotent(msg, text)
1303
Barry Warsaw41075852001-09-23 03:18:13 +00001304 def test_content_type(self):
1305 eq = self.assertEquals
Barry Warsaw2c685062002-06-02 19:09:27 +00001306 unless = self.failUnless
Barry Warsaw41075852001-09-23 03:18:13 +00001307 # Get a message object and reset the seek pointer for other tests
1308 msg, text = self._msgobj('msg_05.txt')
1309 eq(msg.get_type(), 'multipart/report')
1310 # Test the Content-Type: parameters
1311 params = {}
Barry Warsaw65279d02001-09-26 05:47:08 +00001312 for pk, pv in msg.get_params():
Barry Warsaw41075852001-09-23 03:18:13 +00001313 params[pk] = pv
1314 eq(params['report-type'], 'delivery-status')
Barry Warsaw65279d02001-09-26 05:47:08 +00001315 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
Barry Warsaw41075852001-09-23 03:18:13 +00001316 eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
1317 eq(msg.epilogue, '\n\n')
1318 eq(len(msg.get_payload()), 3)
1319 # Make sure the subparts are what we expect
1320 msg1 = msg.get_payload(0)
1321 eq(msg1.get_type(), 'text/plain')
1322 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
1323 msg2 = msg.get_payload(1)
1324 eq(msg2.get_type(), None)
1325 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
1326 msg3 = msg.get_payload(2)
1327 eq(msg3.get_type(), 'message/rfc822')
1328 self.failUnless(isinstance(msg3, Message))
Barry Warsaw2c685062002-06-02 19:09:27 +00001329 payload = msg3.get_payload()
1330 unless(isinstance(payload, ListType))
1331 eq(len(payload), 1)
1332 msg4 = payload[0]
1333 unless(isinstance(msg4, Message))
Barry Warsaw41075852001-09-23 03:18:13 +00001334 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
1335
1336 def test_parser(self):
1337 eq = self.assertEquals
Barry Warsaw2c685062002-06-02 19:09:27 +00001338 unless = self.failUnless
Barry Warsaw41075852001-09-23 03:18:13 +00001339 msg, text = self._msgobj('msg_06.txt')
1340 # Check some of the outer headers
1341 eq(msg.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +00001342 # Make sure the payload is a list of exactly one sub-Message, and that
1343 # that submessage has a type of text/plain
1344 payload = msg.get_payload()
1345 unless(isinstance(payload, ListType))
1346 eq(len(payload), 1)
1347 msg1 = payload[0]
Barry Warsaw41075852001-09-23 03:18:13 +00001348 self.failUnless(isinstance(msg1, Message))
1349 eq(msg1.get_type(), 'text/plain')
1350 self.failUnless(isinstance(msg1.get_payload(), StringType))
1351 eq(msg1.get_payload(), '\n')
Barry Warsaw41075852001-09-23 03:18:13 +00001352
Barry Warsaw08a534d2001-10-04 17:58:50 +00001353
1354# Test various other bits of the package's functionality
Barry Warsaw41075852001-09-23 03:18:13 +00001355class TestMiscellaneous(unittest.TestCase):
1356 def test_message_from_string(self):
1357 fp = openfile('msg_01.txt')
1358 try:
1359 text = fp.read()
1360 finally:
1361 fp.close()
1362 msg = email.message_from_string(text)
1363 s = StringIO()
1364 # Don't wrap/continue long headers since we're trying to test
1365 # idempotency.
1366 g = Generator(s, maxheaderlen=0)
Barry Warsaw2c685062002-06-02 19:09:27 +00001367 g.flatten(msg)
Barry Warsaw41075852001-09-23 03:18:13 +00001368 self.assertEqual(text, s.getvalue())
1369
1370 def test_message_from_file(self):
1371 fp = openfile('msg_01.txt')
1372 try:
1373 text = fp.read()
1374 fp.seek(0)
1375 msg = email.message_from_file(fp)
1376 s = StringIO()
1377 # Don't wrap/continue long headers since we're trying to test
1378 # idempotency.
1379 g = Generator(s, maxheaderlen=0)
Barry Warsaw2c685062002-06-02 19:09:27 +00001380 g.flatten(msg)
Barry Warsaw41075852001-09-23 03:18:13 +00001381 self.assertEqual(text, s.getvalue())
1382 finally:
1383 fp.close()
1384
1385 def test_message_from_string_with_class(self):
1386 unless = self.failUnless
1387 fp = openfile('msg_01.txt')
1388 try:
1389 text = fp.read()
1390 finally:
1391 fp.close()
1392 # Create a subclass
1393 class MyMessage(Message):
1394 pass
Tim Peters527e64f2001-10-04 05:36:56 +00001395
Barry Warsaw41075852001-09-23 03:18:13 +00001396 msg = email.message_from_string(text, MyMessage)
1397 unless(isinstance(msg, MyMessage))
1398 # Try something more complicated
1399 fp = openfile('msg_02.txt')
1400 try:
1401 text = fp.read()
1402 finally:
1403 fp.close()
1404 msg = email.message_from_string(text, MyMessage)
1405 for subpart in msg.walk():
1406 unless(isinstance(subpart, MyMessage))
1407
Barry Warsaw41075852001-09-23 03:18:13 +00001408 def test_message_from_file_with_class(self):
1409 unless = self.failUnless
1410 # Create a subclass
1411 class MyMessage(Message):
1412 pass
Tim Peters527e64f2001-10-04 05:36:56 +00001413
Barry Warsaw41075852001-09-23 03:18:13 +00001414 fp = openfile('msg_01.txt')
1415 try:
1416 msg = email.message_from_file(fp, MyMessage)
1417 finally:
1418 fp.close()
1419 unless(isinstance(msg, MyMessage))
1420 # Try something more complicated
1421 fp = openfile('msg_02.txt')
1422 try:
1423 msg = email.message_from_file(fp, MyMessage)
1424 finally:
1425 fp.close()
1426 for subpart in msg.walk():
1427 unless(isinstance(subpart, MyMessage))
1428
Barry Warsawfee435a2001-10-09 19:23:57 +00001429 def test__all__(self):
1430 module = __import__('email')
1431 all = module.__all__
1432 all.sort()
Barry Warsaw16f90552002-04-16 05:06:42 +00001433 self.assertEqual(all, ['Charset', 'Encoders', 'Errors', 'Generator',
1434 'Header', 'Iterators', 'MIMEAudio',
1435 'MIMEBase', 'MIMEImage', 'MIMEMessage',
Barry Warsaw409a4c02002-04-10 21:01:31 +00001436 'MIMEText', 'Message', 'Parser',
Barry Warsaw16f90552002-04-16 05:06:42 +00001437 'Utils', 'base64MIME',
Barry Warsaw409a4c02002-04-10 21:01:31 +00001438 'message_from_file', 'message_from_string',
1439 'quopriMIME'])
Barry Warsawfee435a2001-10-09 19:23:57 +00001440
Barry Warsaw75edc6a2001-11-09 17:46:17 +00001441 def test_formatdate(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001442 now = time.time()
1443 self.assertEqual(Utils.parsedate(Utils.formatdate(now))[:6],
1444 time.gmtime(now)[:6])
Barry Warsaw75edc6a2001-11-09 17:46:17 +00001445
Barry Warsaw4586d2c2001-11-19 18:38:42 +00001446 def test_formatdate_localtime(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001447 now = time.time()
1448 self.assertEqual(
1449 Utils.parsedate(Utils.formatdate(now, localtime=1))[:6],
1450 time.localtime(now)[:6])
Barry Warsaw75a40fc2001-11-19 16:31:06 +00001451
1452 def test_parsedate_none(self):
Barry Warsaw19c10ca2001-11-13 18:01:37 +00001453 self.assertEqual(Utils.parsedate(''), None)
1454
Barry Warsaweae36ac2001-12-20 16:37:27 +00001455 def test_parseaddr_empty(self):
1456 self.assertEqual(Utils.parseaddr('<>'), ('', ''))
Barry Warsaw409a4c02002-04-10 21:01:31 +00001457 self.assertEqual(Utils.formataddr(Utils.parseaddr('<>')), '')
1458
1459 def test_noquote_dump(self):
1460 self.assertEqual(
1461 Utils.formataddr(('A Silly Person', 'person@dom.ain')),
1462 'A Silly Person <person@dom.ain>')
1463
1464 def test_escape_dump(self):
1465 self.assertEqual(
1466 Utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
1467 r'"A \(Very\) Silly Person" <person@dom.ain>')
1468 a = r'A \(Special\) Person'
1469 b = 'person@dom.ain'
1470 self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
1471
1472 def test_quote_dump(self):
1473 self.assertEqual(
1474 Utils.formataddr(('A Silly; Person', 'person@dom.ain')),
1475 r'"A Silly; Person" <person@dom.ain>')
1476
1477 def test_fix_eols(self):
1478 eq = self.assertEqual
1479 eq(Utils.fix_eols('hello'), 'hello')
1480 eq(Utils.fix_eols('hello\n'), 'hello\r\n')
1481 eq(Utils.fix_eols('hello\r'), 'hello\r\n')
1482 eq(Utils.fix_eols('hello\r\n'), 'hello\r\n')
1483 eq(Utils.fix_eols('hello\n\r'), 'hello\r\n\r\n')
1484
1485 def test_charset_richcomparisons(self):
1486 eq = self.assertEqual
1487 ne = self.failIfEqual
1488 cset1 = Charset()
1489 cset2 = Charset()
1490 eq(cset1, 'us-ascii')
1491 eq(cset1, 'US-ASCII')
1492 eq(cset1, 'Us-AsCiI')
1493 eq('us-ascii', cset1)
1494 eq('US-ASCII', cset1)
1495 eq('Us-AsCiI', cset1)
1496 ne(cset1, 'usascii')
1497 ne(cset1, 'USASCII')
1498 ne(cset1, 'UsAsCiI')
1499 ne('usascii', cset1)
1500 ne('USASCII', cset1)
1501 ne('UsAsCiI', cset1)
1502 eq(cset1, cset2)
1503 eq(cset2, cset1)
Barry Warsaweae36ac2001-12-20 16:37:27 +00001504
Barry Warsaw4be9ecc2002-05-22 01:52:10 +00001505 def test_getaddresses(self):
1506 eq = self.assertEqual
1507 eq(Utils.getaddresses(['aperson@dom.ain (Al Person)',
1508 'Bud Person <bperson@dom.ain>']),
1509 [('Al Person', 'aperson@dom.ain'),
1510 ('Bud Person', 'bperson@dom.ain')])
1511
Barry Warsaw41075852001-09-23 03:18:13 +00001512
Barry Warsaw08a534d2001-10-04 17:58:50 +00001513
1514# Test the iterator/generators
Barry Warsaw41075852001-09-23 03:18:13 +00001515class TestIterators(TestEmailBase):
1516 def test_body_line_iterator(self):
1517 eq = self.assertEqual
1518 # First a simple non-multipart message
1519 msg = self._msgobj('msg_01.txt')
1520 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +00001521 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +00001522 eq(len(lines), 6)
1523 eq(EMPTYSTRING.join(lines), msg.get_payload())
1524 # Now a more complicated multipart
1525 msg = self._msgobj('msg_02.txt')
1526 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +00001527 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +00001528 eq(len(lines), 43)
Barry Warsaw329d3af2002-07-09 02:38:24 +00001529 fp = openfile('msg_19.txt')
1530 try:
1531 eq(EMPTYSTRING.join(lines), fp.read())
1532 finally:
1533 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +00001534
1535 def test_typed_subpart_iterator(self):
1536 eq = self.assertEqual
1537 msg = self._msgobj('msg_04.txt')
1538 it = Iterators.typed_subpart_iterator(msg, 'text')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001539 lines = []
1540 subparts = 0
1541 for subpart in it:
1542 subparts += 1
1543 lines.append(subpart.get_payload())
1544 eq(subparts, 2)
Barry Warsaw41075852001-09-23 03:18:13 +00001545 eq(EMPTYSTRING.join(lines), """\
1546a simple kind of mirror
1547to reflect upon our own
1548a simple kind of mirror
1549to reflect upon our own
1550""")
1551
Barry Warsawcdc632c2001-10-15 04:39:02 +00001552 def test_typed_subpart_iterator_default_type(self):
1553 eq = self.assertEqual
1554 msg = self._msgobj('msg_03.txt')
1555 it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
1556 lines = []
1557 subparts = 0
1558 for subpart in it:
1559 subparts += 1
1560 lines.append(subpart.get_payload())
1561 eq(subparts, 1)
1562 eq(EMPTYSTRING.join(lines), """\
1563
1564Hi,
1565
1566Do you like this message?
1567
1568-Me
1569""")
Barry Warsaw41075852001-09-23 03:18:13 +00001570
Barry Warsaw409a4c02002-04-10 21:01:31 +00001571
Barry Warsaw08a534d2001-10-04 17:58:50 +00001572
Barry Warsaw329d3af2002-07-09 02:38:24 +00001573class TestParsers(TestEmailBase):
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001574 def test_header_parser(self):
1575 eq = self.assertEqual
1576 # Parse only the headers of a complex multipart MIME document
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001577 fp = openfile('msg_02.txt')
Barry Warsaw329d3af2002-07-09 02:38:24 +00001578 try:
1579 msg = HeaderParser().parse(fp)
1580 finally:
1581 fp.close()
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001582 eq(msg['from'], 'ppp-request@zzz.org')
1583 eq(msg['to'], 'ppp@zzz.org')
1584 eq(msg.get_type(), 'multipart/mixed')
1585 eq(msg.is_multipart(), 0)
1586 self.failUnless(isinstance(msg.get_payload(), StringType))
1587
Barry Warsaw409a4c02002-04-10 21:01:31 +00001588 def test_whitespace_continuaton(self):
1589 eq = self.assertEqual
1590 # This message contains a line after the Subject: header that has only
1591 # whitespace, but it is not empty!
1592 msg = email.message_from_string("""\
1593From: aperson@dom.ain
1594To: bperson@dom.ain
1595Subject: the next line has a space on it
Barry Warsaw16f90552002-04-16 05:06:42 +00001596\x20
Barry Warsaw409a4c02002-04-10 21:01:31 +00001597Date: Mon, 8 Apr 2002 15:09:19 -0400
1598Message-ID: spam
1599
1600Here's the message body
1601""")
1602 eq(msg['subject'], 'the next line has a space on it\n ')
1603 eq(msg['message-id'], 'spam')
1604 eq(msg.get_payload(), "Here's the message body\n")
1605
Barry Warsawe0d85c82002-05-19 23:52:54 +00001606 def test_crlf_separation(self):
1607 eq = self.assertEqual
1608 fp = openfile('msg_26.txt')
Barry Warsaw329d3af2002-07-09 02:38:24 +00001609 try:
1610 msg = Parser().parse(fp)
1611 finally:
1612 fp.close()
Barry Warsawe0d85c82002-05-19 23:52:54 +00001613 eq(len(msg.get_payload()), 2)
1614 part1 = msg.get_payload(0)
1615 eq(part1.get_type(), 'text/plain')
1616 eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
1617 part2 = msg.get_payload(1)
1618 eq(part2.get_type(), 'application/riscos')
1619
Barry Warsaw329d3af2002-07-09 02:38:24 +00001620 def test_multipart_digest_with_extra_mime_headers(self):
1621 eq = self.assertEqual
1622 neq = self.ndiffAssertEqual
1623 fp = openfile('msg_28.txt')
1624 try:
1625 msg = email.message_from_file(fp)
1626 finally:
1627 fp.close()
1628 # Structure is:
1629 # multipart/digest
1630 # message/rfc822
1631 # text/plain
1632 # message/rfc822
1633 # text/plain
1634 eq(msg.is_multipart(), 1)
1635 eq(len(msg.get_payload()), 2)
1636 part1 = msg.get_payload(0)
1637 eq(part1.get_type(), 'message/rfc822')
1638 eq(part1.is_multipart(), 1)
1639 eq(len(part1.get_payload()), 1)
1640 part1a = part1.get_payload(0)
1641 eq(part1a.is_multipart(), 0)
1642 eq(part1a.get_type(), 'text/plain')
1643 neq(part1a.get_payload(), 'message 1\n')
1644 # next message/rfc822
1645 part2 = msg.get_payload(1)
1646 eq(part2.get_type(), 'message/rfc822')
1647 eq(part2.is_multipart(), 1)
1648 eq(len(part2.get_payload()), 1)
1649 part2a = part2.get_payload(0)
1650 eq(part2a.is_multipart(), 0)
1651 eq(part2a.get_type(), 'text/plain')
1652 neq(part2a.get_payload(), 'message 2\n')
Barry Warsawb6a92132002-06-28 23:49:33 +00001653
Barry Warsaw409a4c02002-04-10 21:01:31 +00001654
1655
1656class TestBase64(unittest.TestCase):
1657 def test_len(self):
1658 eq = self.assertEqual
1659 eq(base64MIME.base64_len('hello'),
1660 len(base64MIME.encode('hello', eol='')))
1661 for size in range(15):
1662 if size == 0 : bsize = 0
1663 elif size <= 3 : bsize = 4
1664 elif size <= 6 : bsize = 8
1665 elif size <= 9 : bsize = 12
1666 elif size <= 12: bsize = 16
1667 else : bsize = 20
1668 eq(base64MIME.base64_len('x'*size), bsize)
1669
1670 def test_decode(self):
1671 eq = self.assertEqual
1672 eq(base64MIME.decode(''), '')
1673 eq(base64MIME.decode('aGVsbG8='), 'hello')
1674 eq(base64MIME.decode('aGVsbG8=', 'X'), 'hello')
1675 eq(base64MIME.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
1676
1677 def test_encode(self):
1678 eq = self.assertEqual
1679 eq(base64MIME.encode(''), '')
1680 eq(base64MIME.encode('hello'), 'aGVsbG8=\n')
1681 # Test the binary flag
1682 eq(base64MIME.encode('hello\n'), 'aGVsbG8K\n')
1683 eq(base64MIME.encode('hello\n', 0), 'aGVsbG8NCg==\n')
1684 # Test the maxlinelen arg
1685 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40), """\
1686eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1687eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1688eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1689eHh4eCB4eHh4IA==
1690""")
1691 # Test the eol argument
1692 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1693eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1694eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1695eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1696eHh4eCB4eHh4IA==\r
1697""")
Barry Warsaw16f90552002-04-16 05:06:42 +00001698
Barry Warsaw409a4c02002-04-10 21:01:31 +00001699 def test_header_encode(self):
1700 eq = self.assertEqual
1701 he = base64MIME.header_encode
1702 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
1703 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
1704 # Test the charset option
1705 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
1706 # Test the keep_eols flag
1707 eq(he('hello\nworld', keep_eols=1),
1708 '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
1709 # Test the maxlinelen argument
1710 eq(he('xxxx ' * 20, maxlinelen=40), """\
1711=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
1712 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
1713 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
1714 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
1715 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
1716 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1717 # Test the eol argument
1718 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1719=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
1720 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
1721 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
1722 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
1723 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
1724 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1725
1726
1727
1728class TestQuopri(unittest.TestCase):
1729 def setUp(self):
1730 self.hlit = [chr(x) for x in range(ord('a'), ord('z')+1)] + \
1731 [chr(x) for x in range(ord('A'), ord('Z')+1)] + \
1732 [chr(x) for x in range(ord('0'), ord('9')+1)] + \
1733 ['!', '*', '+', '-', '/', ' ']
1734 self.hnon = [chr(x) for x in range(256) if chr(x) not in self.hlit]
1735 assert len(self.hlit) + len(self.hnon) == 256
1736 self.blit = [chr(x) for x in range(ord(' '), ord('~')+1)] + ['\t']
1737 self.blit.remove('=')
1738 self.bnon = [chr(x) for x in range(256) if chr(x) not in self.blit]
1739 assert len(self.blit) + len(self.bnon) == 256
1740
1741 def test_header_quopri_check(self):
1742 for c in self.hlit:
1743 self.failIf(quopriMIME.header_quopri_check(c))
1744 for c in self.hnon:
1745 self.failUnless(quopriMIME.header_quopri_check(c))
1746
1747 def test_body_quopri_check(self):
1748 for c in self.blit:
1749 self.failIf(quopriMIME.body_quopri_check(c))
1750 for c in self.bnon:
1751 self.failUnless(quopriMIME.body_quopri_check(c))
1752
1753 def test_header_quopri_len(self):
1754 eq = self.assertEqual
1755 hql = quopriMIME.header_quopri_len
1756 enc = quopriMIME.header_encode
1757 for s in ('hello', 'h@e@l@l@o@'):
1758 # Empty charset and no line-endings. 7 == RFC chrome
1759 eq(hql(s), len(enc(s, charset='', eol=''))-7)
1760 for c in self.hlit:
1761 eq(hql(c), 1)
1762 for c in self.hnon:
1763 eq(hql(c), 3)
1764
1765 def test_body_quopri_len(self):
1766 eq = self.assertEqual
1767 bql = quopriMIME.body_quopri_len
1768 for c in self.blit:
1769 eq(bql(c), 1)
1770 for c in self.bnon:
1771 eq(bql(c), 3)
1772
1773 def test_quote_unquote_idempotent(self):
1774 for x in range(256):
1775 c = chr(x)
1776 self.assertEqual(quopriMIME.unquote(quopriMIME.quote(c)), c)
1777
1778 def test_header_encode(self):
1779 eq = self.assertEqual
1780 he = quopriMIME.header_encode
1781 eq(he('hello'), '=?iso-8859-1?q?hello?=')
1782 eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
1783 # Test the charset option
1784 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
1785 # Test the keep_eols flag
1786 eq(he('hello\nworld', keep_eols=1), '=?iso-8859-1?q?hello=0Aworld?=')
1787 # Test a non-ASCII character
Jack Jansen1476c272002-04-23 10:52:44 +00001788 eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001789 # Test the maxlinelen argument
1790 eq(he('xxxx ' * 20, maxlinelen=40), """\
1791=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
1792 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
1793 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
1794 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
1795 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
1796 # Test the eol argument
1797 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1798=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
1799 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
1800 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
1801 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
1802 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
1803
1804 def test_decode(self):
1805 eq = self.assertEqual
1806 eq(quopriMIME.decode(''), '')
1807 eq(quopriMIME.decode('hello'), 'hello')
1808 eq(quopriMIME.decode('hello', 'X'), 'hello')
1809 eq(quopriMIME.decode('hello\nworld', 'X'), 'helloXworld')
1810
1811 def test_encode(self):
1812 eq = self.assertEqual
1813 eq(quopriMIME.encode(''), '')
1814 eq(quopriMIME.encode('hello'), 'hello')
1815 # Test the binary flag
1816 eq(quopriMIME.encode('hello\r\nworld'), 'hello\nworld')
1817 eq(quopriMIME.encode('hello\r\nworld', 0), 'hello\nworld')
1818 # Test the maxlinelen arg
1819 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40), """\
1820xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
1821 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
1822x xxxx xxxx xxxx xxxx=20""")
1823 # Test the eol argument
1824 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1825xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
1826 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
1827x xxxx xxxx xxxx xxxx=20""")
1828 eq(quopriMIME.encode("""\
1829one line
1830
1831two line"""), """\
1832one line
1833
1834two line""")
Barry Warsaw16f90552002-04-16 05:06:42 +00001835
Barry Warsaw409a4c02002-04-10 21:01:31 +00001836
1837
1838# Test the Charset class
1839class TestCharset(unittest.TestCase):
1840 def test_idempotent(self):
1841 eq = self.assertEqual
1842 # Make sure us-ascii = no Unicode conversion
1843 c = Charset('us-ascii')
1844 s = 'Hello World!'
1845 sp = c.to_splittable(s)
1846 eq(s, c.from_splittable(sp))
1847 # test 8-bit idempotency with us-ascii
1848 s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
1849 sp = c.to_splittable(s)
1850 eq(s, c.from_splittable(sp))
1851
1852
1853
1854# Test multilingual MIME headers.
Barry Warsawb6a92132002-06-28 23:49:33 +00001855class TestHeader(TestEmailBase):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001856 def test_simple(self):
Barry Warsawb6a92132002-06-28 23:49:33 +00001857 eq = self.ndiffAssertEqual
1858 h = Header('Hello World!')
1859 eq(h.encode(), 'Hello World!')
1860 h.append(' Goodbye World!')
1861 eq(h.encode(), 'Hello World! Goodbye World!')
1862
1863 def test_simple_surprise(self):
1864 eq = self.ndiffAssertEqual
Barry Warsaw409a4c02002-04-10 21:01:31 +00001865 h = Header('Hello World!')
1866 eq(h.encode(), 'Hello World!')
1867 h.append('Goodbye World!')
Barry Warsawb6a92132002-06-28 23:49:33 +00001868 eq(h.encode(), 'Hello World!Goodbye World!')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001869
1870 def test_header_needs_no_decoding(self):
1871 h = 'no decoding needed'
1872 self.assertEqual(decode_header(h), [(h, None)])
1873
1874 def test_long(self):
1875 h = Header("I am the very model of a modern Major-General; I've information vegetable, animal, and mineral; I know the kings of England, and I quote the fights historical from Marathon to Waterloo, in order categorical; I'm very well acquainted, too, with matters mathematical; I understand equations, both the simple and quadratical; about binomial theorem I'm teeming with a lot o' news, with many cheerful facts about the square of the hypotenuse.",
1876 maxlinelen=76)
1877 for l in h.encode().split('\n '):
1878 self.failUnless(len(l) <= 76)
1879
1880 def test_multilingual(self):
1881 eq = self.assertEqual
1882 g = Charset("iso-8859-1")
1883 cz = Charset("iso-8859-2")
1884 utf8 = Charset("utf-8")
1885 g_head = "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
1886 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
1887 utf8_head = u"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
1888 h = Header(g_head, g)
1889 h.append(cz_head, cz)
1890 h.append(utf8_head, utf8)
1891 enc = h.encode()
1892 eq(enc, """=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
1893 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
1894 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
1895 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
1896 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
1897 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
1898 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
1899 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
1900 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
1901 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
1902 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
1903 =?utf-8?b?cyBOdW5zdHVjayBnaXQgdW5k?=
1904 =?utf-8?b?IFNsb3Rlcm1leWVyPyBKYSEgQmVpaGVyaHVuZCBkYXMgT2Rl?=
1905 =?utf-8?b?ciBkaWUgRmxpcHBlcndhbGR0?=
1906 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""")
1907 eq(decode_header(enc),
1908 [(g_head, "iso-8859-1"), (cz_head, "iso-8859-2"),
1909 (utf8_head, "utf-8")])
Barry Warsaw3fdc8892002-06-29 03:27:27 +00001910 # Test for conversion to unicode. BAW: Python 2.1 doesn't support the
1911 # __unicode__() protocol, so do things this way for compatibility.
1912 ustr = h.__unicode__()
1913 # For Python 2.2 and beyond
1914 #ustr = unicode(h)
1915 eq(ustr.encode('utf-8'),
1916 'Die Mieter treten hier ein werden mit einem Foerderband '
1917 'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
1918 'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
1919 'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
1920 'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
1921 '\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
1922 '\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
1923 '\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
1924 '\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
1925 '\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
1926 '\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
1927 '\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
1928 '\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
1929 'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
1930 'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
1931 '\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001932
Barry Warsawe0d85c82002-05-19 23:52:54 +00001933 def test_explicit_maxlinelen(self):
Barry Warsawb6a92132002-06-28 23:49:33 +00001934 eq = self.ndiffAssertEqual
Barry Warsawe0d85c82002-05-19 23:52:54 +00001935 hstr = 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
1936 h = Header(hstr)
1937 eq(h.encode(), '''\
Barry Warsawb6a92132002-06-28 23:49:33 +00001938A very long line that must get split to something other than at the 76th
1939 character boundary to test the non-default behavior''')
Barry Warsawe0d85c82002-05-19 23:52:54 +00001940 h = Header(hstr, header_name='Subject')
1941 eq(h.encode(), '''\
1942A very long line that must get split to something other than at the
Barry Warsawb6a92132002-06-28 23:49:33 +00001943 76th character boundary to test the non-default behavior''')
Barry Warsawe0d85c82002-05-19 23:52:54 +00001944 h = Header(hstr, maxlinelen=1024, header_name='Subject')
1945 eq(h.encode(), hstr)
1946
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001947
Barry Warsaw9546e792002-06-29 05:58:45 +00001948# Test RFC 2231 header parameters decoding
1949class TestRFC2231(TestEmailBase):
1950 def test_get_param(self):
1951 eq = self.assertEqual
1952 msg = self._msgobj('msg_29.txt')
1953 eq(msg.get_param('title'),
1954 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
1955 eq(msg.get_param('title', unquote=0),
1956 ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
1957
1958
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001959
Barry Warsawc9ad32c2002-04-15 22:14:06 +00001960def _testclasses():
1961 mod = sys.modules[__name__]
1962 return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
1963
1964
Barry Warsaw41075852001-09-23 03:18:13 +00001965def suite():
1966 suite = unittest.TestSuite()
Barry Warsawc9ad32c2002-04-15 22:14:06 +00001967 for testclass in _testclasses():
1968 suite.addTest(unittest.makeSuite(testclass))
Barry Warsaw41075852001-09-23 03:18:13 +00001969 return suite
1970
1971
Barry Warsawc9ad32c2002-04-15 22:14:06 +00001972def test_main():
1973 for testclass in _testclasses():
1974 test_support.run_unittest(testclass)
1975
1976
Barry Warsaw08a534d2001-10-04 17:58:50 +00001977
Guido van Rossum78f0dd32001-12-07 21:07:08 +00001978if __name__ == '__main__':
Barry Warsaw409a4c02002-04-10 21:01:31 +00001979 unittest.main(defaultTest='suite')