blob: 1c5add0fe84a6483630766b20c26d1a632a1c389 [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
31
32def openfile(filename):
33 path = os.path.join(os.path.dirname(test.regrtest.__file__),
34 'data', filename)
35 return open(path)
36
37
38
39# 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
50
51# Test various aspects of the Message class's API
52class TestMessageAPI(TestEmailBase):
53 def test_get_charsets(self):
54 eq = self.assertEqual
55
Barry Warsaw65279d02001-09-26 05:47:08 +000056 msg = self._msgobj('msg_08.txt')
57 charsets = msg.get_charsets()
58 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +000059
Barry Warsaw65279d02001-09-26 05:47:08 +000060 msg = self._msgobj('msg_09.txt')
61 charsets = msg.get_charsets('dingbat')
62 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
63 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +000064
Barry Warsaw65279d02001-09-26 05:47:08 +000065 msg = self._msgobj('msg_12.txt')
66 charsets = msg.get_charsets()
67 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
68 'iso-8859-3', 'us-ascii', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +000069
70 def test_get_filename(self):
71 eq = self.assertEqual
72
Barry Warsaw65279d02001-09-26 05:47:08 +000073 msg = self._msgobj('msg_04.txt')
74 filenames = [p.get_filename() for p in msg.get_payload()]
Barry Warsaw41075852001-09-23 03:18:13 +000075 eq(filenames, ['msg.txt', 'msg.txt'])
76
Barry Warsaw65279d02001-09-26 05:47:08 +000077 msg = self._msgobj('msg_07.txt')
78 subpart = msg.get_payload(1)
Barry Warsaw41075852001-09-23 03:18:13 +000079 eq(subpart.get_filename(), 'dingusfish.gif')
80
81 def test_get_boundary(self):
82 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +000083 msg = self._msgobj('msg_07.txt')
Barry Warsaw41075852001-09-23 03:18:13 +000084 # No quotes!
Barry Warsaw65279d02001-09-26 05:47:08 +000085 eq(msg.get_boundary(), 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +000086
87 def test_set_boundary(self):
88 eq = self.assertEqual
89 # This one has no existing boundary parameter, but the Content-Type:
90 # header appears fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +000091 msg = self._msgobj('msg_01.txt')
92 msg.set_boundary('BOUNDARY')
93 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +000094 eq(header.lower(), 'content-type')
95 eq(value, 'text/plain; charset=us-ascii; boundary="BOUNDARY"')
96 # This one has a Content-Type: header, with a boundary, stuck in the
97 # middle of its headers. Make sure the order is preserved; it should
98 # be fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +000099 msg = self._msgobj('msg_04.txt')
100 msg.set_boundary('BOUNDARY')
101 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000102 eq(header.lower(), 'content-type')
103 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
104 # And this one has no Content-Type: header at all.
Barry Warsaw65279d02001-09-26 05:47:08 +0000105 msg = self._msgobj('msg_03.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000106 self.assertRaises(Errors.HeaderParseError,
Barry Warsaw65279d02001-09-26 05:47:08 +0000107 msg.set_boundary, 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000108
109 def test_get_decoded_payload(self):
110 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000111 msg = self._msgobj('msg_10.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000112 # The outer message is a multipart
Barry Warsaw65279d02001-09-26 05:47:08 +0000113 eq(msg.get_payload(decode=1), None)
Barry Warsaw41075852001-09-23 03:18:13 +0000114 # Subpart 1 is 7bit encoded
Barry Warsaw65279d02001-09-26 05:47:08 +0000115 eq(msg.get_payload(0).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000116 'This is a 7bit encoded message.\n')
117 # Subpart 2 is quopri
Barry Warsaw65279d02001-09-26 05:47:08 +0000118 eq(msg.get_payload(1).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000119 '\xa1This is a Quoted Printable encoded message!\n')
120 # Subpart 3 is base64
Barry Warsaw65279d02001-09-26 05:47:08 +0000121 eq(msg.get_payload(2).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000122 'This is a Base64 encoded message.')
123 # Subpart 4 has no Content-Transfer-Encoding: header.
Barry Warsaw65279d02001-09-26 05:47:08 +0000124 eq(msg.get_payload(3).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000125 'This has no Content-Transfer-Encoding: header.\n')
126
127 def test_decoded_generator(self):
128 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000129 msg = self._msgobj('msg_07.txt')
130 fp = openfile('msg_17.txt')
131 try:
132 text = fp.read()
133 finally:
134 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +0000135 s = StringIO()
136 g = DecodedGenerator(s)
Barry Warsaw65279d02001-09-26 05:47:08 +0000137 g(msg)
138 eq(s.getvalue(), text)
Barry Warsaw41075852001-09-23 03:18:13 +0000139
140 def test__contains__(self):
141 msg = Message()
142 msg['From'] = 'Me'
143 msg['to'] = 'You'
144 # Check for case insensitivity
145 self.failUnless('from' in msg)
146 self.failUnless('From' in msg)
147 self.failUnless('FROM' in msg)
148 self.failUnless('to' in msg)
149 self.failUnless('To' in msg)
150 self.failUnless('TO' in msg)
151
152 def test_as_string(self):
153 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000154 msg = self._msgobj('msg_01.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000155 fp = openfile('msg_01.txt')
156 try:
157 text = fp.read()
158 finally:
159 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000160 eq(text, msg.as_string())
161 fullrepr = str(msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000162 lines = fullrepr.split('\n')
163 self.failUnless(lines[0].startswith('From '))
164 eq(text, NL.join(lines[1:]))
165
166 def test_bad_param(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000167 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000168 self.assertEqual(msg.get_param('baz'), '')
169
170 def test_missing_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000171 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000172 self.assertEqual(msg.get_filename(), None)
173
174 def test_bogus_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000175 msg = email.message_from_string(
176 "Content-Disposition: blarg; filename\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000177 self.assertEqual(msg.get_filename(), '')
178
179 def test_missing_boundary(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000180 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000181 self.assertEqual(msg.get_boundary(), None)
182
Barry Warsaw65279d02001-09-26 05:47:08 +0000183 def test_get_params(self):
184 eq = self.assertEqual
185 msg = email.message_from_string(
186 'X-Header: foo=one; bar=two; baz=three\n')
187 eq(msg.get_params(header='x-header'),
188 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
189 msg = email.message_from_string(
190 'X-Header: foo; bar=one; baz=two\n')
191 eq(msg.get_params(header='x-header'),
192 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
193 eq(msg.get_params(), None)
194 msg = email.message_from_string(
195 'X-Header: foo; bar="one"; baz=two\n')
196 eq(msg.get_params(header='x-header'),
197 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
198
199 def test_get_param(self):
200 eq = self.assertEqual
201 msg = email.message_from_string(
202 "X-Header: foo=one; bar=two; baz=three\n")
203 eq(msg.get_param('bar', header='x-header'), 'two')
204 eq(msg.get_param('quuz', header='x-header'), None)
205 eq(msg.get_param('quuz'), None)
206 msg = email.message_from_string(
207 'X-Header: foo; bar="one"; baz=two\n')
208 eq(msg.get_param('foo', header='x-header'), '')
209 eq(msg.get_param('bar', header='x-header'), 'one')
210 eq(msg.get_param('baz', header='x-header'), 'two')
211
212 def test_has_key(self):
213 msg = email.message_from_string('Header: exists')
214 self.failUnless(msg.has_key('header'))
215 self.failUnless(msg.has_key('Header'))
216 self.failUnless(msg.has_key('HEADER'))
217 self.failIf(msg.has_key('headeri'))
218
Barry Warsaw41075852001-09-23 03:18:13 +0000219
220
221# Test the email.Encoders module
222class TestEncoders(unittest.TestCase):
223 def test_encode_noop(self):
224 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000225 msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
Barry Warsaw41075852001-09-23 03:18:13 +0000226 eq(msg.get_payload(), 'hello world\n')
227 eq(msg['content-transfer-encoding'], None)
228
229 def test_encode_7bit(self):
230 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000231 msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000232 eq(msg.get_payload(), 'hello world\n')
233 eq(msg['content-transfer-encoding'], '7bit')
Barry Warsaw65279d02001-09-26 05:47:08 +0000234 msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000235 eq(msg.get_payload(), 'hello \x7f world\n')
236 eq(msg['content-transfer-encoding'], '7bit')
237
238 def test_encode_8bit(self):
239 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000240 msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000241 eq(msg.get_payload(), 'hello \x80 world\n')
242 eq(msg['content-transfer-encoding'], '8bit')
243
244 def test_encode_base64(self):
245 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000246 msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
Barry Warsaw41075852001-09-23 03:18:13 +0000247 eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
248 eq(msg['content-transfer-encoding'], 'base64')
249
250 def test_encode_quoted_printable(self):
251 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000252 msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
Barry Warsaw41075852001-09-23 03:18:13 +0000253 eq(msg.get_payload(), 'hello=20world\n')
254 eq(msg['content-transfer-encoding'], 'quoted-printable')
255
256
257
258class TestLongHeaders(unittest.TestCase):
259 def test_header_splitter(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000260 msg = MIMEText('')
Barry Warsaw41075852001-09-23 03:18:13 +0000261 # It'd be great if we could use add_header() here, but that doesn't
262 # guarantee an order of the parameters.
263 msg['X-Foobar-Spoink-Defrobnit'] = (
264 'wasnipoop; giraffes="very-long-necked-animals"; '
265 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
266 sfp = StringIO()
267 g = Generator(sfp)
268 g(msg)
269 self.assertEqual(sfp.getvalue(), '''\
270Content-Type: text/plain; charset="us-ascii"
271MIME-Version: 1.0
272Content-Transfer-Encoding: 7bit
273X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
274 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
275
276''')
277
278
279
280class TestFromMangling(unittest.TestCase):
281 def setUp(self):
282 self.msg = Message()
283 self.msg['From'] = 'aaa@bbb.org'
284 self.msg.add_payload("""\
285From the desk of A.A.A.:
286Blah blah blah
287""")
288
289 def test_mangled_from(self):
290 s = StringIO()
291 g = Generator(s, mangle_from_=1)
292 g(self.msg)
293 self.assertEqual(s.getvalue(), """\
294From: aaa@bbb.org
295
296>From the desk of A.A.A.:
297Blah blah blah
298""")
299
300 def test_dont_mangle_from(self):
301 s = StringIO()
302 g = Generator(s, mangle_from_=0)
303 g(self.msg)
304 self.assertEqual(s.getvalue(), """\
305From: aaa@bbb.org
306
307From the desk of A.A.A.:
308Blah blah blah
309""")
310
311
312
Barry Warsaw65279d02001-09-26 05:47:08 +0000313# Test the basic MIMEImage class
314class TestMIMEImage(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000315 def setUp(self):
316 fp = openfile('PyBanner048.gif')
317 try:
318 self._imgdata = fp.read()
319 finally:
320 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000321 self._im = MIMEImage(self._imgdata)
Barry Warsaw41075852001-09-23 03:18:13 +0000322
323 def test_guess_minor_type(self):
324 self.assertEqual(self._im.get_type(), 'image/gif')
325
326 def test_encoding(self):
327 payload = self._im.get_payload()
328 self.assertEqual(base64.decodestring(payload), self._imgdata)
329
330 def checkSetMinor(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000331 im = MIMEImage(self._imgdata, 'fish')
Barry Warsaw41075852001-09-23 03:18:13 +0000332 self.assertEqual(im.get_type(), 'image/fish')
333
334 def test_custom_encoder(self):
335 eq = self.assertEqual
336 def encoder(msg):
337 orig = msg.get_payload()
338 msg.set_payload(0)
339 msg['Content-Transfer-Encoding'] = 'broken64'
Barry Warsaw65279d02001-09-26 05:47:08 +0000340 im = MIMEImage(self._imgdata, _encoder=encoder)
Barry Warsaw41075852001-09-23 03:18:13 +0000341 eq(im.get_payload(), 0)
342 eq(im['content-transfer-encoding'], 'broken64')
343
344 def test_add_header(self):
345 eq = self.assertEqual
346 unless = self.failUnless
347 self._im.add_header('Content-Disposition', 'attachment',
348 filename='dingusfish.gif')
349 eq(self._im['content-disposition'],
350 'attachment; filename="dingusfish.gif"')
351 eq(self._im.get_params(header='content-disposition'),
Barry Warsaw65279d02001-09-26 05:47:08 +0000352 [('attachment', ''), ('filename', 'dingusfish.gif')])
Barry Warsaw41075852001-09-23 03:18:13 +0000353 eq(self._im.get_param('filename', header='content-disposition'),
354 'dingusfish.gif')
355 missing = []
Barry Warsaw65279d02001-09-26 05:47:08 +0000356 eq(self._im.get_param('attachment', header='content-disposition'), '')
357 unless(self._im.get_param('foo', failobj=missing,
Barry Warsaw41075852001-09-23 03:18:13 +0000358 header='content-disposition') is missing)
359 # Try some missing stuff
360 unless(self._im.get_param('foobar', missing) is missing)
361 unless(self._im.get_param('attachment', missing,
362 header='foobar') is missing)
363
364
365
Barry Warsaw65279d02001-09-26 05:47:08 +0000366# Test the basic MIMEText class
367class TestMIMEText(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000368 def setUp(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000369 self._msg = MIMEText('hello there')
Barry Warsaw41075852001-09-23 03:18:13 +0000370
371 def test_types(self):
372 eq = self.assertEqual
373 unless = self.failUnless
374 eq(self._msg.get_type(), 'text/plain')
375 eq(self._msg.get_param('charset'), 'us-ascii')
376 missing = []
377 unless(self._msg.get_param('foobar', missing) is missing)
378 unless(self._msg.get_param('charset', missing, header='foobar')
379 is missing)
380
381 def test_payload(self):
382 self.assertEqual(self._msg.get_payload(), 'hello there\n')
383 self.failUnless(not self._msg.is_multipart())
384
385
386
387class TestMultipartMixed(unittest.TestCase):
388 def setUp(self):
389 fp = openfile('PyBanner048.gif')
390 try:
391 data = fp.read()
392 finally:
393 fp.close()
394
395 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
Barry Warsaw65279d02001-09-26 05:47:08 +0000396 image = MIMEImage(data, name='dingusfish.gif')
Barry Warsaw41075852001-09-23 03:18:13 +0000397 image.add_header('content-disposition', 'attachment',
398 filename='dingusfish.gif')
Barry Warsaw65279d02001-09-26 05:47:08 +0000399 intro = MIMEText('''\
Barry Warsaw41075852001-09-23 03:18:13 +0000400Hi there,
401
402This is the dingus fish.
403''')
404 container.add_payload(intro)
405 container.add_payload(image)
406 container['From'] = 'Barry <barry@digicool.com>'
407 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
408 container['Subject'] = 'Here is your dingus fish'
409
410 now = 987809702.54848599
411 timetuple = time.localtime(now)
412 if timetuple[-1] == 0:
413 tzsecs = time.timezone
414 else:
415 tzsecs = time.altzone
416 if tzsecs > 0:
417 sign = '-'
418 else:
419 sign = '+'
420 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
421 container['Date'] = time.strftime(
422 '%a, %d %b %Y %H:%M:%S',
423 time.localtime(now)) + tzoffset
424 self._msg = container
425 self._im = image
426 self._txt = intro
427
428 def test_hierarchy(self):
429 # convenience
430 eq = self.assertEqual
431 unless = self.failUnless
432 raises = self.assertRaises
433 # tests
434 m = self._msg
435 unless(m.is_multipart())
436 eq(m.get_type(), 'multipart/mixed')
437 eq(len(m.get_payload()), 2)
438 raises(IndexError, m.get_payload, 2)
439 m0 = m.get_payload(0)
440 m1 = m.get_payload(1)
441 unless(m0 is self._txt)
442 unless(m1 is self._im)
443 eq(m.get_payload(), [m0, m1])
444 unless(not m0.is_multipart())
445 unless(not m1.is_multipart())
446
447
448
449class TestNonConformant(TestEmailBase):
450 def test_parse_missing_minor_type(self):
451 eq = self.assertEqual
452 msg = self._msgobj('msg_14.txt')
453 eq(msg.get_type(), 'text')
454 eq(msg.get_main_type(), 'text')
455 self.failUnless(msg.get_subtype() is None)
456
457 def test_bogus_boundary(self):
458 fp = openfile('msg_15.txt')
459 try:
460 data = fp.read()
461 finally:
462 fp.close()
463 p = Parser()
464 # Note, under a future non-strict parsing mode, this would parse the
465 # message into the intended message tree.
466 self.assertRaises(Errors.BoundaryError, p.parsestr, data)
467
468
469
470class TestRFC2047(unittest.TestCase):
471 def test_iso_8859_1(self):
472 eq = self.assertEqual
473 s = '=?iso-8859-1?q?this=20is=20some=20text?='
474 eq(Utils.decode(s), 'this is some text')
475 s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
476 eq(Utils.decode(s), u'Keld_J\xf8rn_Simonsen')
477 s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
478 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
479 eq(Utils.decode(s), 'If you can read this you understand the example.')
480 s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
481 eq(Utils.decode(s),
482 u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
483 s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
484 eq(Utils.decode(s), u'this is some text')
485
486 def test_encode_header(self):
487 eq = self.assertEqual
488 s = 'this is some text'
489 eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
490 s = 'Keld_J\xf8rn_Simonsen'
491 eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
492 s1 = 'If you can read this yo'
493 s2 = 'u understand the example.'
494 eq(Utils.encode(s1, encoding='b'),
495 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
496 eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
497 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
498
499
500
Barry Warsaw65279d02001-09-26 05:47:08 +0000501class TestMIMEMessage(TestEmailBase):
Barry Warsaw41075852001-09-23 03:18:13 +0000502 def setUp(self):
503 fp = openfile('msg_11.txt')
504 self._text = fp.read()
505 fp.close()
506
507 def test_type_error(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000508 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
Barry Warsaw41075852001-09-23 03:18:13 +0000509
510 def test_valid_argument(self):
511 eq = self.assertEqual
512 subject = 'A sub-message'
513 m = Message()
514 m['Subject'] = subject
Barry Warsaw65279d02001-09-26 05:47:08 +0000515 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000516 eq(r.get_type(), 'message/rfc822')
517 self.failUnless(r.get_payload() is m)
518 eq(r.get_payload()['subject'], subject)
519
520 def test_generate(self):
521 # First craft the message to be encapsulated
522 m = Message()
523 m['Subject'] = 'An enclosed message'
524 m.add_payload('Here is the body of the message.\n')
Barry Warsaw65279d02001-09-26 05:47:08 +0000525 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000526 r['Subject'] = 'The enclosing message'
527 s = StringIO()
528 g = Generator(s)
529 g(r)
Barry Warsaw65279d02001-09-26 05:47:08 +0000530 self.assertEqual(s.getvalue(), """\
531Content-Type: message/rfc822
532MIME-Version: 1.0
533Subject: The enclosing message
534
535Subject: An enclosed message
536
537Here is the body of the message.
538""")
539
540 def test_parse_message_rfc822(self):
541 eq = self.assertEqual
542 msg = self._msgobj('msg_11.txt')
543 eq(msg.get_type(), 'message/rfc822')
544 eq(len(msg.get_payload()), 1)
545 submsg = msg.get_payload()
546 self.failUnless(isinstance(submsg, Message))
547 eq(submsg['subject'], 'An enclosed message')
548 eq(submsg.get_payload(), 'Here is the body of the message.\n')
549
550 def test_dsn(self):
551 eq = self.assertEqual
552 unless = self.failUnless
553 # msg 16 is a Delivery Status Notification, see RFC XXXX
554 msg = self._msgobj('msg_16.txt')
555 eq(msg.get_type(), 'multipart/report')
556 unless(msg.is_multipart())
557 eq(len(msg.get_payload()), 3)
558 # Subpart 1 is a text/plain, human readable section
559 subpart = msg.get_payload(0)
560 eq(subpart.get_type(), 'text/plain')
561 eq(subpart.get_payload(), """\
562This report relates to a message you sent with the following header fields:
563
564 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
565 Date: Sun, 23 Sep 2001 20:10:55 -0700
566 From: "Ian T. Henry" <henryi@oxy.edu>
567 To: SoCal Raves <scr@socal-raves.org>
568 Subject: [scr] yeah for Ians!!
569
570Your message cannot be delivered to the following recipients:
571
572 Recipient address: jangel1@cougar.noc.ucla.edu
573 Reason: recipient reached disk quota
574
575""")
576 # Subpart 2 contains the machine parsable DSN information. It
577 # consists of two blocks of headers, represented by two nested Message
578 # objects.
579 subpart = msg.get_payload(1)
580 eq(subpart.get_type(), 'message/delivery-status')
581 eq(len(subpart.get_payload()), 2)
582 # message/delivery-status should treat each block as a bunch of
583 # headers, i.e. a bunch of Message objects.
584 dsn1 = subpart.get_payload(0)
585 unless(isinstance(dsn1, Message))
586 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
587 eq(dsn1.get_param('dns', header='reporting-mta'), '')
588 # Try a missing one <wink>
589 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
590 dsn2 = subpart.get_payload(1)
591 unless(isinstance(dsn2, Message))
592 eq(dsn2['action'], 'failed')
593 eq(dsn2.get_params(header='original-recipient'),
594 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
595 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
596 # Subpart 3 is the original message
597 subpart = msg.get_payload(2)
598 eq(subpart.get_type(), 'message/rfc822')
599 subsubpart = subpart.get_payload()
600 unless(isinstance(subsubpart, Message))
601 eq(subsubpart.get_type(), 'text/plain')
602 eq(subsubpart['message-id'],
603 '<002001c144a6$8752e060$56104586@oxy.edu>')
Barry Warsaw41075852001-09-23 03:18:13 +0000604
605
606
607class TestIdempotent(unittest.TestCase):
608 def _msgobj(self, filename):
609 fp = openfile(filename)
610 try:
611 data = fp.read()
612 finally:
613 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000614 msg = email.message_from_string(data)
615 return msg, data
Barry Warsaw41075852001-09-23 03:18:13 +0000616
617 def _idempotent(self, msg, text):
618 eq = self.assertEquals
619 s = StringIO()
620 g = Generator(s, maxheaderlen=0)
621 g(msg)
622 eq(text, s.getvalue())
623
624 def test_parse_text_message(self):
625 eq = self.assertEquals
626 msg, text = self._msgobj('msg_01.txt')
627 eq(msg.get_type(), 'text/plain')
628 eq(msg.get_main_type(), 'text')
629 eq(msg.get_subtype(), 'plain')
Barry Warsaw65279d02001-09-26 05:47:08 +0000630 eq(msg.get_params()[1], ('charset', 'us-ascii'))
Barry Warsaw41075852001-09-23 03:18:13 +0000631 eq(msg.get_param('charset'), 'us-ascii')
632 eq(msg.preamble, None)
633 eq(msg.epilogue, None)
634 self._idempotent(msg, text)
635
636 def test_parse_untyped_message(self):
637 eq = self.assertEquals
638 msg, text = self._msgobj('msg_03.txt')
639 eq(msg.get_type(), None)
640 eq(msg.get_params(), None)
641 eq(msg.get_param('charset'), None)
642 self._idempotent(msg, text)
643
644 def test_simple_multipart(self):
645 msg, text = self._msgobj('msg_04.txt')
646 self._idempotent(msg, text)
647
648 def test_MIME_digest(self):
649 msg, text = self._msgobj('msg_02.txt')
650 self._idempotent(msg, text)
651
652 def test_mixed_with_image(self):
653 msg, text = self._msgobj('msg_06.txt')
654 self._idempotent(msg, text)
655
656 def test_multipart_report(self):
657 msg, text = self._msgobj('msg_05.txt')
658 self._idempotent(msg, text)
Barry Warsaw65279d02001-09-26 05:47:08 +0000659
660 def test_dsn(self):
661 msg, text = self._msgobj('msg_16.txt')
662 self._idempotent(msg, text)
Barry Warsaw41075852001-09-23 03:18:13 +0000663
664 def test_content_type(self):
665 eq = self.assertEquals
666 # Get a message object and reset the seek pointer for other tests
667 msg, text = self._msgobj('msg_05.txt')
668 eq(msg.get_type(), 'multipart/report')
669 # Test the Content-Type: parameters
670 params = {}
Barry Warsaw65279d02001-09-26 05:47:08 +0000671 for pk, pv in msg.get_params():
Barry Warsaw41075852001-09-23 03:18:13 +0000672 params[pk] = pv
673 eq(params['report-type'], 'delivery-status')
Barry Warsaw65279d02001-09-26 05:47:08 +0000674 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
Barry Warsaw41075852001-09-23 03:18:13 +0000675 eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
676 eq(msg.epilogue, '\n\n')
677 eq(len(msg.get_payload()), 3)
678 # Make sure the subparts are what we expect
679 msg1 = msg.get_payload(0)
680 eq(msg1.get_type(), 'text/plain')
681 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
682 msg2 = msg.get_payload(1)
683 eq(msg2.get_type(), None)
684 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
685 msg3 = msg.get_payload(2)
686 eq(msg3.get_type(), 'message/rfc822')
687 self.failUnless(isinstance(msg3, Message))
688 msg4 = msg3.get_payload()
689 self.failUnless(isinstance(msg4, Message))
690 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
691
692 def test_parser(self):
693 eq = self.assertEquals
694 msg, text = self._msgobj('msg_06.txt')
695 # Check some of the outer headers
696 eq(msg.get_type(), 'message/rfc822')
697 # Make sure there's exactly one thing in the payload and that's a
698 # sub-Message object of type text/plain
699 msg1 = msg.get_payload()
700 self.failUnless(isinstance(msg1, Message))
701 eq(msg1.get_type(), 'text/plain')
702 self.failUnless(isinstance(msg1.get_payload(), StringType))
703 eq(msg1.get_payload(), '\n')
704
705
706
707class TestMiscellaneous(unittest.TestCase):
708 def test_message_from_string(self):
709 fp = openfile('msg_01.txt')
710 try:
711 text = fp.read()
712 finally:
713 fp.close()
714 msg = email.message_from_string(text)
715 s = StringIO()
716 # Don't wrap/continue long headers since we're trying to test
717 # idempotency.
718 g = Generator(s, maxheaderlen=0)
719 g(msg)
720 self.assertEqual(text, s.getvalue())
721
722 def test_message_from_file(self):
723 fp = openfile('msg_01.txt')
724 try:
725 text = fp.read()
726 fp.seek(0)
727 msg = email.message_from_file(fp)
728 s = StringIO()
729 # Don't wrap/continue long headers since we're trying to test
730 # idempotency.
731 g = Generator(s, maxheaderlen=0)
732 g(msg)
733 self.assertEqual(text, s.getvalue())
734 finally:
735 fp.close()
736
737 def test_message_from_string_with_class(self):
738 unless = self.failUnless
739 fp = openfile('msg_01.txt')
740 try:
741 text = fp.read()
742 finally:
743 fp.close()
744 # Create a subclass
745 class MyMessage(Message):
746 pass
747
748 msg = email.message_from_string(text, MyMessage)
749 unless(isinstance(msg, MyMessage))
750 # Try something more complicated
751 fp = openfile('msg_02.txt')
752 try:
753 text = fp.read()
754 finally:
755 fp.close()
756 msg = email.message_from_string(text, MyMessage)
757 for subpart in msg.walk():
758 unless(isinstance(subpart, MyMessage))
759
760
761 def test_message_from_file_with_class(self):
762 unless = self.failUnless
763 # Create a subclass
764 class MyMessage(Message):
765 pass
766
767 fp = openfile('msg_01.txt')
768 try:
769 msg = email.message_from_file(fp, MyMessage)
770 finally:
771 fp.close()
772 unless(isinstance(msg, MyMessage))
773 # Try something more complicated
774 fp = openfile('msg_02.txt')
775 try:
776 msg = email.message_from_file(fp, MyMessage)
777 finally:
778 fp.close()
779 for subpart in msg.walk():
780 unless(isinstance(subpart, MyMessage))
781
782
783
784class TestIterators(TestEmailBase):
785 def test_body_line_iterator(self):
786 eq = self.assertEqual
787 # First a simple non-multipart message
788 msg = self._msgobj('msg_01.txt')
789 it = Iterators.body_line_iterator(msg)
790 lines = []
791 for line in it:
792 lines.append(line)
793 eq(len(lines), 6)
794 eq(EMPTYSTRING.join(lines), msg.get_payload())
795 # Now a more complicated multipart
796 msg = self._msgobj('msg_02.txt')
797 it = Iterators.body_line_iterator(msg)
798 lines = []
799 for line in it:
800 lines.append(line)
801 eq(len(lines), 43)
802 eq(EMPTYSTRING.join(lines), """\
803Send Ppp mailing list submissions to
804 ppp@zzz.org
805
806To subscribe or unsubscribe via the World Wide Web, visit
807 http://www.zzz.org/mailman/listinfo/ppp
808or, via email, send a message with subject or body 'help' to
809 ppp-request@zzz.org
810
811You can reach the person managing the list at
812 ppp-admin@zzz.org
813
814When replying, please edit your Subject line so it is more specific
815than "Re: Contents of Ppp digest..."
816
817Today's Topics:
818
819 1. testing #1 (Barry A. Warsaw)
820 2. testing #2 (Barry A. Warsaw)
821 3. testing #3 (Barry A. Warsaw)
822 4. testing #4 (Barry A. Warsaw)
823 5. testing #5 (Barry A. Warsaw)
824
825hello
826
827
828hello
829
830
831hello
832
833
834hello
835
836
837hello
838
839
840
841_______________________________________________
842Ppp mailing list
843Ppp@zzz.org
844http://www.zzz.org/mailman/listinfo/ppp
845
846""")
847
848 def test_typed_subpart_iterator(self):
849 eq = self.assertEqual
850 msg = self._msgobj('msg_04.txt')
851 it = Iterators.typed_subpart_iterator(msg, 'text')
852 lines = []
853 subparts = 0
854 for subpart in it:
855 subparts += 1
856 lines.append(subpart.get_payload())
857 eq(subparts, 2)
858 eq(EMPTYSTRING.join(lines), """\
859a simple kind of mirror
860to reflect upon our own
861a simple kind of mirror
862to reflect upon our own
863""")
864
865
866
867def suite():
868 suite = unittest.TestSuite()
869 suite.addTest(unittest.makeSuite(TestMessageAPI))
870 suite.addTest(unittest.makeSuite(TestEncoders))
871 suite.addTest(unittest.makeSuite(TestLongHeaders))
872 suite.addTest(unittest.makeSuite(TestFromMangling))
Barry Warsaw65279d02001-09-26 05:47:08 +0000873 suite.addTest(unittest.makeSuite(TestMIMEImage))
874 suite.addTest(unittest.makeSuite(TestMIMEText))
Barry Warsaw41075852001-09-23 03:18:13 +0000875 suite.addTest(unittest.makeSuite(TestMultipartMixed))
876 suite.addTest(unittest.makeSuite(TestNonConformant))
877 suite.addTest(unittest.makeSuite(TestRFC2047))
Barry Warsaw65279d02001-09-26 05:47:08 +0000878 suite.addTest(unittest.makeSuite(TestMIMEMessage))
Barry Warsaw41075852001-09-23 03:18:13 +0000879 suite.addTest(unittest.makeSuite(TestIdempotent))
880 suite.addTest(unittest.makeSuite(TestMiscellaneous))
881 suite.addTest(unittest.makeSuite(TestIterators))
882 return suite
883
884
885
886if __name__ == '__main__':
887 unittest.main(defaultTest='suite')
888else:
889 from test_support import run_suite
890 run_suite(suite())