blob: 06aae2b46777cd63550afc408e22158dd21993c3 [file] [log] [blame]
Barry Warsaw41075852001-09-23 03:18:13 +00001# Copyright (C) 2001 Python Software Foundation
2# email package unit tests
3
4import os
5import time
6import unittest
7import base64
8from cStringIO import StringIO
9from types import StringType
10
11import email
12
13from email.Parser import Parser
14from email.Generator import Generator, DecodedGenerator
15from email.Message import Message
Barry Warsaw65279d02001-09-26 05:47:08 +000016from email.MIMEText import MIMEText
17from email.MIMEImage import MIMEImage
Barry Warsaw41075852001-09-23 03:18:13 +000018from email.MIMEBase import MIMEBase
Barry Warsaw65279d02001-09-26 05:47:08 +000019from email.MIMEMessage import MIMEMessage
Barry Warsaw41075852001-09-23 03:18:13 +000020from email import Utils
21from email import Errors
22from email import Encoders
23from email import Iterators
24
25import test.regrtest
26
27NL = '\n'
28EMPTYSTRING = ''
29
30
Barry Warsaw08a534d2001-10-04 17:58:50 +000031
Barry Warsaw41075852001-09-23 03:18:13 +000032def openfile(filename):
33 path = os.path.join(os.path.dirname(test.regrtest.__file__),
34 'data', filename)
35 return open(path)
36
37
Barry Warsaw08a534d2001-10-04 17:58:50 +000038
Barry Warsaw41075852001-09-23 03:18:13 +000039# Base test class
40class TestEmailBase(unittest.TestCase):
41 def _msgobj(self, filename):
42 fp = openfile(filename)
43 try:
Barry Warsaw65279d02001-09-26 05:47:08 +000044 msg = email.message_from_file(fp)
Barry Warsaw41075852001-09-23 03:18:13 +000045 finally:
46 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +000047 return msg
Barry Warsaw41075852001-09-23 03:18:13 +000048
49
Barry Warsaw08a534d2001-10-04 17:58:50 +000050
Barry Warsaw41075852001-09-23 03:18:13 +000051# Test various aspects of the Message class's API
52class TestMessageAPI(TestEmailBase):
Barry Warsaw2f6a0b02001-10-09 15:49:35 +000053 def test_get_all(self):
54 eq = self.assertEqual
55 msg = self._msgobj('msg_20.txt')
56 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
57 eq(msg.get_all('xx', 'n/a'), 'n/a')
58
Barry Warsaw41075852001-09-23 03:18:13 +000059 def test_get_charsets(self):
60 eq = self.assertEqual
Tim Peters527e64f2001-10-04 05:36:56 +000061
Barry Warsaw65279d02001-09-26 05:47:08 +000062 msg = self._msgobj('msg_08.txt')
63 charsets = msg.get_charsets()
64 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +000065
Barry Warsaw65279d02001-09-26 05:47:08 +000066 msg = self._msgobj('msg_09.txt')
67 charsets = msg.get_charsets('dingbat')
68 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
69 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +000070
Barry Warsaw65279d02001-09-26 05:47:08 +000071 msg = self._msgobj('msg_12.txt')
72 charsets = msg.get_charsets()
73 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
74 'iso-8859-3', 'us-ascii', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +000075
76 def test_get_filename(self):
77 eq = self.assertEqual
78
Barry Warsaw65279d02001-09-26 05:47:08 +000079 msg = self._msgobj('msg_04.txt')
80 filenames = [p.get_filename() for p in msg.get_payload()]
Barry Warsaw41075852001-09-23 03:18:13 +000081 eq(filenames, ['msg.txt', 'msg.txt'])
82
Barry Warsaw65279d02001-09-26 05:47:08 +000083 msg = self._msgobj('msg_07.txt')
84 subpart = msg.get_payload(1)
Barry Warsaw41075852001-09-23 03:18:13 +000085 eq(subpart.get_filename(), 'dingusfish.gif')
86
87 def test_get_boundary(self):
88 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +000089 msg = self._msgobj('msg_07.txt')
Barry Warsaw41075852001-09-23 03:18:13 +000090 # No quotes!
Barry Warsaw65279d02001-09-26 05:47:08 +000091 eq(msg.get_boundary(), 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +000092
93 def test_set_boundary(self):
94 eq = self.assertEqual
95 # This one has no existing boundary parameter, but the Content-Type:
96 # header appears fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +000097 msg = self._msgobj('msg_01.txt')
98 msg.set_boundary('BOUNDARY')
99 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000100 eq(header.lower(), 'content-type')
101 eq(value, 'text/plain; charset=us-ascii; boundary="BOUNDARY"')
102 # This one has a Content-Type: header, with a boundary, stuck in the
103 # middle of its headers. Make sure the order is preserved; it should
104 # be fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000105 msg = self._msgobj('msg_04.txt')
106 msg.set_boundary('BOUNDARY')
107 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000108 eq(header.lower(), 'content-type')
109 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
110 # And this one has no Content-Type: header at all.
Barry Warsaw65279d02001-09-26 05:47:08 +0000111 msg = self._msgobj('msg_03.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000112 self.assertRaises(Errors.HeaderParseError,
Barry Warsaw65279d02001-09-26 05:47:08 +0000113 msg.set_boundary, 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000114
115 def test_get_decoded_payload(self):
116 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000117 msg = self._msgobj('msg_10.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000118 # The outer message is a multipart
Barry Warsaw65279d02001-09-26 05:47:08 +0000119 eq(msg.get_payload(decode=1), None)
Barry Warsaw41075852001-09-23 03:18:13 +0000120 # Subpart 1 is 7bit encoded
Barry Warsaw65279d02001-09-26 05:47:08 +0000121 eq(msg.get_payload(0).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000122 'This is a 7bit encoded message.\n')
123 # Subpart 2 is quopri
Barry Warsaw65279d02001-09-26 05:47:08 +0000124 eq(msg.get_payload(1).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000125 '\xa1This is a Quoted Printable encoded message!\n')
126 # Subpart 3 is base64
Barry Warsaw65279d02001-09-26 05:47:08 +0000127 eq(msg.get_payload(2).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000128 'This is a Base64 encoded message.')
129 # Subpart 4 has no Content-Transfer-Encoding: header.
Barry Warsaw65279d02001-09-26 05:47:08 +0000130 eq(msg.get_payload(3).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000131 'This has no Content-Transfer-Encoding: header.\n')
132
133 def test_decoded_generator(self):
134 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000135 msg = self._msgobj('msg_07.txt')
136 fp = openfile('msg_17.txt')
137 try:
138 text = fp.read()
139 finally:
140 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +0000141 s = StringIO()
142 g = DecodedGenerator(s)
Barry Warsaw65279d02001-09-26 05:47:08 +0000143 g(msg)
144 eq(s.getvalue(), text)
Barry Warsaw41075852001-09-23 03:18:13 +0000145
146 def test__contains__(self):
147 msg = Message()
148 msg['From'] = 'Me'
149 msg['to'] = 'You'
150 # Check for case insensitivity
151 self.failUnless('from' in msg)
152 self.failUnless('From' in msg)
153 self.failUnless('FROM' in msg)
154 self.failUnless('to' in msg)
155 self.failUnless('To' in msg)
156 self.failUnless('TO' in msg)
157
158 def test_as_string(self):
159 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000160 msg = self._msgobj('msg_01.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000161 fp = openfile('msg_01.txt')
162 try:
163 text = fp.read()
164 finally:
165 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000166 eq(text, msg.as_string())
167 fullrepr = str(msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000168 lines = fullrepr.split('\n')
169 self.failUnless(lines[0].startswith('From '))
170 eq(text, NL.join(lines[1:]))
171
172 def test_bad_param(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000173 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000174 self.assertEqual(msg.get_param('baz'), '')
175
176 def test_missing_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000177 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000178 self.assertEqual(msg.get_filename(), None)
179
180 def test_bogus_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000181 msg = email.message_from_string(
182 "Content-Disposition: blarg; filename\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000183 self.assertEqual(msg.get_filename(), '')
Tim Peters527e64f2001-10-04 05:36:56 +0000184
Barry Warsaw41075852001-09-23 03:18:13 +0000185 def test_missing_boundary(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000186 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000187 self.assertEqual(msg.get_boundary(), None)
188
Barry Warsaw65279d02001-09-26 05:47:08 +0000189 def test_get_params(self):
190 eq = self.assertEqual
191 msg = email.message_from_string(
192 'X-Header: foo=one; bar=two; baz=three\n')
193 eq(msg.get_params(header='x-header'),
194 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
195 msg = email.message_from_string(
196 'X-Header: foo; bar=one; baz=two\n')
197 eq(msg.get_params(header='x-header'),
198 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
199 eq(msg.get_params(), None)
200 msg = email.message_from_string(
201 'X-Header: foo; bar="one"; baz=two\n')
202 eq(msg.get_params(header='x-header'),
203 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
204
205 def test_get_param(self):
206 eq = self.assertEqual
207 msg = email.message_from_string(
208 "X-Header: foo=one; bar=two; baz=three\n")
209 eq(msg.get_param('bar', header='x-header'), 'two')
210 eq(msg.get_param('quuz', header='x-header'), None)
211 eq(msg.get_param('quuz'), None)
212 msg = email.message_from_string(
213 'X-Header: foo; bar="one"; baz=two\n')
214 eq(msg.get_param('foo', header='x-header'), '')
215 eq(msg.get_param('bar', header='x-header'), 'one')
216 eq(msg.get_param('baz', header='x-header'), 'two')
217
218 def test_has_key(self):
219 msg = email.message_from_string('Header: exists')
220 self.failUnless(msg.has_key('header'))
221 self.failUnless(msg.has_key('Header'))
222 self.failUnless(msg.has_key('HEADER'))
223 self.failIf(msg.has_key('headeri'))
224
Barry Warsaw41075852001-09-23 03:18:13 +0000225
Barry Warsaw08a534d2001-10-04 17:58:50 +0000226
Barry Warsaw41075852001-09-23 03:18:13 +0000227# Test the email.Encoders module
228class TestEncoders(unittest.TestCase):
229 def test_encode_noop(self):
230 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000231 msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
Barry Warsaw41075852001-09-23 03:18:13 +0000232 eq(msg.get_payload(), 'hello world\n')
233 eq(msg['content-transfer-encoding'], None)
234
235 def test_encode_7bit(self):
236 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000237 msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000238 eq(msg.get_payload(), 'hello world\n')
239 eq(msg['content-transfer-encoding'], '7bit')
Barry Warsaw65279d02001-09-26 05:47:08 +0000240 msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000241 eq(msg.get_payload(), 'hello \x7f world\n')
242 eq(msg['content-transfer-encoding'], '7bit')
243
244 def test_encode_8bit(self):
245 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000246 msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000247 eq(msg.get_payload(), 'hello \x80 world\n')
248 eq(msg['content-transfer-encoding'], '8bit')
249
250 def test_encode_base64(self):
251 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000252 msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
Barry Warsaw41075852001-09-23 03:18:13 +0000253 eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
254 eq(msg['content-transfer-encoding'], 'base64')
255
256 def test_encode_quoted_printable(self):
257 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000258 msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
Barry Warsaw41075852001-09-23 03:18:13 +0000259 eq(msg.get_payload(), 'hello=20world\n')
260 eq(msg['content-transfer-encoding'], 'quoted-printable')
261
262
Barry Warsaw08a534d2001-10-04 17:58:50 +0000263
264# Test long header wrapping
Barry Warsaw41075852001-09-23 03:18:13 +0000265class TestLongHeaders(unittest.TestCase):
266 def test_header_splitter(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000267 msg = MIMEText('')
Barry Warsaw41075852001-09-23 03:18:13 +0000268 # It'd be great if we could use add_header() here, but that doesn't
269 # guarantee an order of the parameters.
270 msg['X-Foobar-Spoink-Defrobnit'] = (
271 'wasnipoop; giraffes="very-long-necked-animals"; '
272 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
273 sfp = StringIO()
274 g = Generator(sfp)
275 g(msg)
Barry Warsaw08a534d2001-10-04 17:58:50 +0000276 self.assertEqual(sfp.getvalue(), openfile('msg_18.txt').read())
Barry Warsaw41075852001-09-23 03:18:13 +0000277
278
Barry Warsaw08a534d2001-10-04 17:58:50 +0000279
280# Test mangling of "From " lines in the body of a message
Barry Warsaw41075852001-09-23 03:18:13 +0000281class TestFromMangling(unittest.TestCase):
282 def setUp(self):
283 self.msg = Message()
284 self.msg['From'] = 'aaa@bbb.org'
285 self.msg.add_payload("""\
286From the desk of A.A.A.:
287Blah blah blah
288""")
289
290 def test_mangled_from(self):
291 s = StringIO()
292 g = Generator(s, mangle_from_=1)
293 g(self.msg)
294 self.assertEqual(s.getvalue(), """\
295From: aaa@bbb.org
296
297>From the desk of A.A.A.:
298Blah blah blah
299""")
300
301 def test_dont_mangle_from(self):
302 s = StringIO()
303 g = Generator(s, mangle_from_=0)
304 g(self.msg)
305 self.assertEqual(s.getvalue(), """\
306From: aaa@bbb.org
307
308From the desk of A.A.A.:
309Blah blah blah
310""")
311
312
Barry Warsaw08a534d2001-10-04 17:58:50 +0000313
Barry Warsaw65279d02001-09-26 05:47:08 +0000314# Test the basic MIMEImage class
315class TestMIMEImage(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000316 def setUp(self):
317 fp = openfile('PyBanner048.gif')
318 try:
319 self._imgdata = fp.read()
320 finally:
321 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000322 self._im = MIMEImage(self._imgdata)
Barry Warsaw41075852001-09-23 03:18:13 +0000323
324 def test_guess_minor_type(self):
325 self.assertEqual(self._im.get_type(), 'image/gif')
326
327 def test_encoding(self):
328 payload = self._im.get_payload()
329 self.assertEqual(base64.decodestring(payload), self._imgdata)
330
331 def checkSetMinor(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000332 im = MIMEImage(self._imgdata, 'fish')
Barry Warsaw41075852001-09-23 03:18:13 +0000333 self.assertEqual(im.get_type(), 'image/fish')
334
335 def test_custom_encoder(self):
336 eq = self.assertEqual
337 def encoder(msg):
338 orig = msg.get_payload()
339 msg.set_payload(0)
340 msg['Content-Transfer-Encoding'] = 'broken64'
Barry Warsaw65279d02001-09-26 05:47:08 +0000341 im = MIMEImage(self._imgdata, _encoder=encoder)
Barry Warsaw41075852001-09-23 03:18:13 +0000342 eq(im.get_payload(), 0)
343 eq(im['content-transfer-encoding'], 'broken64')
344
345 def test_add_header(self):
346 eq = self.assertEqual
347 unless = self.failUnless
348 self._im.add_header('Content-Disposition', 'attachment',
349 filename='dingusfish.gif')
350 eq(self._im['content-disposition'],
351 'attachment; filename="dingusfish.gif"')
352 eq(self._im.get_params(header='content-disposition'),
Barry Warsaw65279d02001-09-26 05:47:08 +0000353 [('attachment', ''), ('filename', 'dingusfish.gif')])
Barry Warsaw41075852001-09-23 03:18:13 +0000354 eq(self._im.get_param('filename', header='content-disposition'),
355 'dingusfish.gif')
356 missing = []
Barry Warsaw65279d02001-09-26 05:47:08 +0000357 eq(self._im.get_param('attachment', header='content-disposition'), '')
358 unless(self._im.get_param('foo', failobj=missing,
Barry Warsaw41075852001-09-23 03:18:13 +0000359 header='content-disposition') is missing)
360 # Try some missing stuff
361 unless(self._im.get_param('foobar', missing) is missing)
362 unless(self._im.get_param('attachment', missing,
363 header='foobar') is missing)
364
365
Barry Warsaw08a534d2001-10-04 17:58:50 +0000366
Barry Warsaw65279d02001-09-26 05:47:08 +0000367# Test the basic MIMEText class
368class TestMIMEText(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000369 def setUp(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000370 self._msg = MIMEText('hello there')
Barry Warsaw41075852001-09-23 03:18:13 +0000371
372 def test_types(self):
373 eq = self.assertEqual
374 unless = self.failUnless
375 eq(self._msg.get_type(), 'text/plain')
376 eq(self._msg.get_param('charset'), 'us-ascii')
377 missing = []
378 unless(self._msg.get_param('foobar', missing) is missing)
379 unless(self._msg.get_param('charset', missing, header='foobar')
380 is missing)
381
382 def test_payload(self):
383 self.assertEqual(self._msg.get_payload(), 'hello there\n')
384 self.failUnless(not self._msg.is_multipart())
385
386
Barry Warsaw08a534d2001-10-04 17:58:50 +0000387
388# Test a more complicated multipart/mixed type message
Barry Warsaw41075852001-09-23 03:18:13 +0000389class TestMultipartMixed(unittest.TestCase):
390 def setUp(self):
391 fp = openfile('PyBanner048.gif')
392 try:
393 data = fp.read()
394 finally:
395 fp.close()
396
397 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
Barry Warsaw65279d02001-09-26 05:47:08 +0000398 image = MIMEImage(data, name='dingusfish.gif')
Barry Warsaw41075852001-09-23 03:18:13 +0000399 image.add_header('content-disposition', 'attachment',
400 filename='dingusfish.gif')
Barry Warsaw65279d02001-09-26 05:47:08 +0000401 intro = MIMEText('''\
Barry Warsaw41075852001-09-23 03:18:13 +0000402Hi there,
403
404This is the dingus fish.
405''')
406 container.add_payload(intro)
407 container.add_payload(image)
408 container['From'] = 'Barry <barry@digicool.com>'
409 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
410 container['Subject'] = 'Here is your dingus fish'
Tim Peters527e64f2001-10-04 05:36:56 +0000411
Barry Warsaw41075852001-09-23 03:18:13 +0000412 now = 987809702.54848599
413 timetuple = time.localtime(now)
414 if timetuple[-1] == 0:
415 tzsecs = time.timezone
416 else:
417 tzsecs = time.altzone
418 if tzsecs > 0:
419 sign = '-'
420 else:
421 sign = '+'
422 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
423 container['Date'] = time.strftime(
424 '%a, %d %b %Y %H:%M:%S',
425 time.localtime(now)) + tzoffset
426 self._msg = container
427 self._im = image
428 self._txt = intro
429
430 def test_hierarchy(self):
431 # convenience
432 eq = self.assertEqual
433 unless = self.failUnless
434 raises = self.assertRaises
435 # tests
436 m = self._msg
437 unless(m.is_multipart())
438 eq(m.get_type(), 'multipart/mixed')
439 eq(len(m.get_payload()), 2)
440 raises(IndexError, m.get_payload, 2)
441 m0 = m.get_payload(0)
442 m1 = m.get_payload(1)
443 unless(m0 is self._txt)
444 unless(m1 is self._im)
445 eq(m.get_payload(), [m0, m1])
446 unless(not m0.is_multipart())
447 unless(not m1.is_multipart())
448
449
Barry Warsaw08a534d2001-10-04 17:58:50 +0000450
451# Test some badly formatted messages
Barry Warsaw41075852001-09-23 03:18:13 +0000452class TestNonConformant(TestEmailBase):
453 def test_parse_missing_minor_type(self):
454 eq = self.assertEqual
455 msg = self._msgobj('msg_14.txt')
456 eq(msg.get_type(), 'text')
457 eq(msg.get_main_type(), 'text')
458 self.failUnless(msg.get_subtype() is None)
459
460 def test_bogus_boundary(self):
461 fp = openfile('msg_15.txt')
462 try:
463 data = fp.read()
464 finally:
465 fp.close()
466 p = Parser()
467 # Note, under a future non-strict parsing mode, this would parse the
468 # message into the intended message tree.
469 self.assertRaises(Errors.BoundaryError, p.parsestr, data)
470
471
Barry Warsaw08a534d2001-10-04 17:58:50 +0000472
473# Test RFC 2047 header encoding and decoding
Barry Warsaw41075852001-09-23 03:18:13 +0000474class TestRFC2047(unittest.TestCase):
475 def test_iso_8859_1(self):
476 eq = self.assertEqual
477 s = '=?iso-8859-1?q?this=20is=20some=20text?='
478 eq(Utils.decode(s), 'this is some text')
479 s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
480 eq(Utils.decode(s), u'Keld_J\xf8rn_Simonsen')
481 s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
482 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
483 eq(Utils.decode(s), 'If you can read this you understand the example.')
484 s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
485 eq(Utils.decode(s),
486 u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
487 s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
488 eq(Utils.decode(s), u'this is some text')
489
490 def test_encode_header(self):
491 eq = self.assertEqual
492 s = 'this is some text'
493 eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
494 s = 'Keld_J\xf8rn_Simonsen'
495 eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
496 s1 = 'If you can read this yo'
497 s2 = 'u understand the example.'
498 eq(Utils.encode(s1, encoding='b'),
499 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
500 eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
501 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
502
503
Barry Warsaw08a534d2001-10-04 17:58:50 +0000504
505# Test the MIMEMessage class
Barry Warsaw65279d02001-09-26 05:47:08 +0000506class TestMIMEMessage(TestEmailBase):
Barry Warsaw41075852001-09-23 03:18:13 +0000507 def setUp(self):
508 fp = openfile('msg_11.txt')
509 self._text = fp.read()
510 fp.close()
511
512 def test_type_error(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000513 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
Barry Warsaw41075852001-09-23 03:18:13 +0000514
515 def test_valid_argument(self):
516 eq = self.assertEqual
517 subject = 'A sub-message'
518 m = Message()
519 m['Subject'] = subject
Barry Warsaw65279d02001-09-26 05:47:08 +0000520 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000521 eq(r.get_type(), 'message/rfc822')
522 self.failUnless(r.get_payload() is m)
523 eq(r.get_payload()['subject'], subject)
524
525 def test_generate(self):
526 # First craft the message to be encapsulated
527 m = Message()
528 m['Subject'] = 'An enclosed message'
529 m.add_payload('Here is the body of the message.\n')
Barry Warsaw65279d02001-09-26 05:47:08 +0000530 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000531 r['Subject'] = 'The enclosing message'
532 s = StringIO()
533 g = Generator(s)
534 g(r)
Barry Warsaw65279d02001-09-26 05:47:08 +0000535 self.assertEqual(s.getvalue(), """\
536Content-Type: message/rfc822
537MIME-Version: 1.0
538Subject: The enclosing message
539
540Subject: An enclosed message
541
542Here is the body of the message.
543""")
544
545 def test_parse_message_rfc822(self):
546 eq = self.assertEqual
547 msg = self._msgobj('msg_11.txt')
548 eq(msg.get_type(), 'message/rfc822')
549 eq(len(msg.get_payload()), 1)
550 submsg = msg.get_payload()
551 self.failUnless(isinstance(submsg, Message))
552 eq(submsg['subject'], 'An enclosed message')
553 eq(submsg.get_payload(), 'Here is the body of the message.\n')
554
555 def test_dsn(self):
556 eq = self.assertEqual
557 unless = self.failUnless
558 # msg 16 is a Delivery Status Notification, see RFC XXXX
559 msg = self._msgobj('msg_16.txt')
560 eq(msg.get_type(), 'multipart/report')
561 unless(msg.is_multipart())
562 eq(len(msg.get_payload()), 3)
563 # Subpart 1 is a text/plain, human readable section
564 subpart = msg.get_payload(0)
565 eq(subpart.get_type(), 'text/plain')
566 eq(subpart.get_payload(), """\
567This report relates to a message you sent with the following header fields:
568
569 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
570 Date: Sun, 23 Sep 2001 20:10:55 -0700
571 From: "Ian T. Henry" <henryi@oxy.edu>
572 To: SoCal Raves <scr@socal-raves.org>
573 Subject: [scr] yeah for Ians!!
574
575Your message cannot be delivered to the following recipients:
576
577 Recipient address: jangel1@cougar.noc.ucla.edu
578 Reason: recipient reached disk quota
579
580""")
581 # Subpart 2 contains the machine parsable DSN information. It
582 # consists of two blocks of headers, represented by two nested Message
583 # objects.
584 subpart = msg.get_payload(1)
585 eq(subpart.get_type(), 'message/delivery-status')
586 eq(len(subpart.get_payload()), 2)
587 # message/delivery-status should treat each block as a bunch of
588 # headers, i.e. a bunch of Message objects.
589 dsn1 = subpart.get_payload(0)
590 unless(isinstance(dsn1, Message))
591 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
592 eq(dsn1.get_param('dns', header='reporting-mta'), '')
593 # Try a missing one <wink>
594 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
595 dsn2 = subpart.get_payload(1)
596 unless(isinstance(dsn2, Message))
597 eq(dsn2['action'], 'failed')
598 eq(dsn2.get_params(header='original-recipient'),
599 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
600 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
601 # Subpart 3 is the original message
602 subpart = msg.get_payload(2)
603 eq(subpart.get_type(), 'message/rfc822')
604 subsubpart = subpart.get_payload()
605 unless(isinstance(subsubpart, Message))
606 eq(subsubpart.get_type(), 'text/plain')
607 eq(subsubpart['message-id'],
608 '<002001c144a6$8752e060$56104586@oxy.edu>')
Barry Warsaw41075852001-09-23 03:18:13 +0000609
610
Barry Warsaw08a534d2001-10-04 17:58:50 +0000611
612# A general test of parser->model->generator idempotency. IOW, read a message
613# in, parse it into a message object tree, then without touching the tree,
614# regenerate the plain text. The original text and the transformed text
615# should be identical. Note: that we ignore the Unix-From since that may
616# contain a changed date.
Barry Warsaw41075852001-09-23 03:18:13 +0000617class TestIdempotent(unittest.TestCase):
618 def _msgobj(self, filename):
619 fp = openfile(filename)
620 try:
621 data = fp.read()
622 finally:
623 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000624 msg = email.message_from_string(data)
625 return msg, data
Barry Warsaw41075852001-09-23 03:18:13 +0000626
627 def _idempotent(self, msg, text):
628 eq = self.assertEquals
629 s = StringIO()
630 g = Generator(s, maxheaderlen=0)
631 g(msg)
632 eq(text, s.getvalue())
633
634 def test_parse_text_message(self):
635 eq = self.assertEquals
636 msg, text = self._msgobj('msg_01.txt')
637 eq(msg.get_type(), 'text/plain')
638 eq(msg.get_main_type(), 'text')
639 eq(msg.get_subtype(), 'plain')
Barry Warsaw65279d02001-09-26 05:47:08 +0000640 eq(msg.get_params()[1], ('charset', 'us-ascii'))
Barry Warsaw41075852001-09-23 03:18:13 +0000641 eq(msg.get_param('charset'), 'us-ascii')
642 eq(msg.preamble, None)
643 eq(msg.epilogue, None)
644 self._idempotent(msg, text)
645
646 def test_parse_untyped_message(self):
647 eq = self.assertEquals
648 msg, text = self._msgobj('msg_03.txt')
649 eq(msg.get_type(), None)
650 eq(msg.get_params(), None)
651 eq(msg.get_param('charset'), None)
652 self._idempotent(msg, text)
653
654 def test_simple_multipart(self):
655 msg, text = self._msgobj('msg_04.txt')
656 self._idempotent(msg, text)
657
658 def test_MIME_digest(self):
659 msg, text = self._msgobj('msg_02.txt')
660 self._idempotent(msg, text)
661
662 def test_mixed_with_image(self):
663 msg, text = self._msgobj('msg_06.txt')
664 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +0000665
Barry Warsaw41075852001-09-23 03:18:13 +0000666 def test_multipart_report(self):
667 msg, text = self._msgobj('msg_05.txt')
668 self._idempotent(msg, text)
Barry Warsaw65279d02001-09-26 05:47:08 +0000669
670 def test_dsn(self):
671 msg, text = self._msgobj('msg_16.txt')
672 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +0000673
Barry Warsaw41075852001-09-23 03:18:13 +0000674 def test_content_type(self):
675 eq = self.assertEquals
676 # Get a message object and reset the seek pointer for other tests
677 msg, text = self._msgobj('msg_05.txt')
678 eq(msg.get_type(), 'multipart/report')
679 # Test the Content-Type: parameters
680 params = {}
Barry Warsaw65279d02001-09-26 05:47:08 +0000681 for pk, pv in msg.get_params():
Barry Warsaw41075852001-09-23 03:18:13 +0000682 params[pk] = pv
683 eq(params['report-type'], 'delivery-status')
Barry Warsaw65279d02001-09-26 05:47:08 +0000684 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
Barry Warsaw41075852001-09-23 03:18:13 +0000685 eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
686 eq(msg.epilogue, '\n\n')
687 eq(len(msg.get_payload()), 3)
688 # Make sure the subparts are what we expect
689 msg1 = msg.get_payload(0)
690 eq(msg1.get_type(), 'text/plain')
691 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
692 msg2 = msg.get_payload(1)
693 eq(msg2.get_type(), None)
694 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
695 msg3 = msg.get_payload(2)
696 eq(msg3.get_type(), 'message/rfc822')
697 self.failUnless(isinstance(msg3, Message))
698 msg4 = msg3.get_payload()
699 self.failUnless(isinstance(msg4, Message))
700 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
701
702 def test_parser(self):
703 eq = self.assertEquals
704 msg, text = self._msgobj('msg_06.txt')
705 # Check some of the outer headers
706 eq(msg.get_type(), 'message/rfc822')
707 # Make sure there's exactly one thing in the payload and that's a
708 # sub-Message object of type text/plain
709 msg1 = msg.get_payload()
710 self.failUnless(isinstance(msg1, Message))
711 eq(msg1.get_type(), 'text/plain')
712 self.failUnless(isinstance(msg1.get_payload(), StringType))
713 eq(msg1.get_payload(), '\n')
Barry Warsaw41075852001-09-23 03:18:13 +0000714
Tim Peters527e64f2001-10-04 05:36:56 +0000715
Barry Warsaw08a534d2001-10-04 17:58:50 +0000716
717# Test various other bits of the package's functionality
Barry Warsaw41075852001-09-23 03:18:13 +0000718class TestMiscellaneous(unittest.TestCase):
719 def test_message_from_string(self):
720 fp = openfile('msg_01.txt')
721 try:
722 text = fp.read()
723 finally:
724 fp.close()
725 msg = email.message_from_string(text)
726 s = StringIO()
727 # Don't wrap/continue long headers since we're trying to test
728 # idempotency.
729 g = Generator(s, maxheaderlen=0)
730 g(msg)
731 self.assertEqual(text, s.getvalue())
732
733 def test_message_from_file(self):
734 fp = openfile('msg_01.txt')
735 try:
736 text = fp.read()
737 fp.seek(0)
738 msg = email.message_from_file(fp)
739 s = StringIO()
740 # Don't wrap/continue long headers since we're trying to test
741 # idempotency.
742 g = Generator(s, maxheaderlen=0)
743 g(msg)
744 self.assertEqual(text, s.getvalue())
745 finally:
746 fp.close()
747
748 def test_message_from_string_with_class(self):
749 unless = self.failUnless
750 fp = openfile('msg_01.txt')
751 try:
752 text = fp.read()
753 finally:
754 fp.close()
755 # Create a subclass
756 class MyMessage(Message):
757 pass
Tim Peters527e64f2001-10-04 05:36:56 +0000758
Barry Warsaw41075852001-09-23 03:18:13 +0000759 msg = email.message_from_string(text, MyMessage)
760 unless(isinstance(msg, MyMessage))
761 # Try something more complicated
762 fp = openfile('msg_02.txt')
763 try:
764 text = fp.read()
765 finally:
766 fp.close()
767 msg = email.message_from_string(text, MyMessage)
768 for subpart in msg.walk():
769 unless(isinstance(subpart, MyMessage))
770
771
772 def test_message_from_file_with_class(self):
773 unless = self.failUnless
774 # Create a subclass
775 class MyMessage(Message):
776 pass
Tim Peters527e64f2001-10-04 05:36:56 +0000777
Barry Warsaw41075852001-09-23 03:18:13 +0000778 fp = openfile('msg_01.txt')
779 try:
780 msg = email.message_from_file(fp, MyMessage)
781 finally:
782 fp.close()
783 unless(isinstance(msg, MyMessage))
784 # Try something more complicated
785 fp = openfile('msg_02.txt')
786 try:
787 msg = email.message_from_file(fp, MyMessage)
788 finally:
789 fp.close()
790 for subpart in msg.walk():
791 unless(isinstance(subpart, MyMessage))
792
793
Barry Warsaw08a534d2001-10-04 17:58:50 +0000794
795# Test the iterator/generators
Barry Warsaw41075852001-09-23 03:18:13 +0000796class TestIterators(TestEmailBase):
797 def test_body_line_iterator(self):
798 eq = self.assertEqual
799 # First a simple non-multipart message
800 msg = self._msgobj('msg_01.txt')
801 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +0000802 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +0000803 eq(len(lines), 6)
804 eq(EMPTYSTRING.join(lines), msg.get_payload())
805 # Now a more complicated multipart
806 msg = self._msgobj('msg_02.txt')
807 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +0000808 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +0000809 eq(len(lines), 43)
Barry Warsaw08a534d2001-10-04 17:58:50 +0000810 eq(EMPTYSTRING.join(lines), openfile('msg_19.txt').read())
Barry Warsaw41075852001-09-23 03:18:13 +0000811
812 def test_typed_subpart_iterator(self):
813 eq = self.assertEqual
814 msg = self._msgobj('msg_04.txt')
815 it = Iterators.typed_subpart_iterator(msg, 'text')
Barry Warsawd1de6ea2001-10-04 18:18:37 +0000816 lines = [subpart.get_payload() for subpart in it]
817 eq(len(lines), 2)
Barry Warsaw41075852001-09-23 03:18:13 +0000818 eq(EMPTYSTRING.join(lines), """\
819a simple kind of mirror
820to reflect upon our own
821a simple kind of mirror
822to reflect upon our own
823""")
824
825
Barry Warsaw08a534d2001-10-04 17:58:50 +0000826
Barry Warsaw41075852001-09-23 03:18:13 +0000827def suite():
828 suite = unittest.TestSuite()
829 suite.addTest(unittest.makeSuite(TestMessageAPI))
830 suite.addTest(unittest.makeSuite(TestEncoders))
831 suite.addTest(unittest.makeSuite(TestLongHeaders))
832 suite.addTest(unittest.makeSuite(TestFromMangling))
Barry Warsaw65279d02001-09-26 05:47:08 +0000833 suite.addTest(unittest.makeSuite(TestMIMEImage))
834 suite.addTest(unittest.makeSuite(TestMIMEText))
Barry Warsaw41075852001-09-23 03:18:13 +0000835 suite.addTest(unittest.makeSuite(TestMultipartMixed))
836 suite.addTest(unittest.makeSuite(TestNonConformant))
837 suite.addTest(unittest.makeSuite(TestRFC2047))
Barry Warsaw65279d02001-09-26 05:47:08 +0000838 suite.addTest(unittest.makeSuite(TestMIMEMessage))
Barry Warsaw41075852001-09-23 03:18:13 +0000839 suite.addTest(unittest.makeSuite(TestIdempotent))
840 suite.addTest(unittest.makeSuite(TestMiscellaneous))
841 suite.addTest(unittest.makeSuite(TestIterators))
842 return suite
843
844
Barry Warsaw08a534d2001-10-04 17:58:50 +0000845
Barry Warsaw41075852001-09-23 03:18:13 +0000846if __name__ == '__main__':
847 unittest.main(defaultTest='suite')
848else:
849 from test_support import run_suite
850 run_suite(suite())