1 # Copyright (C) 2001 Python Software Foundation
2 # email package unit tests
8 from cStringIO
import StringIO
9 from types
import StringType
13 from email
.Parser
import Parser
, HeaderParser
14 from email
.Generator
import Generator
, DecodedGenerator
15 from email
.Message
import Message
16 from email
.MIMEAudio
import MIMEAudio
17 from email
.MIMEText
import MIMEText
18 from email
.MIMEImage
import MIMEImage
19 from email
.MIMEBase
import MIMEBase
20 from email
.MIMEMessage
import MIMEMessage
21 from email
import Utils
22 from email
import Errors
23 from email
import Encoders
24 from email
import Iterators
26 from test_support
import findfile
, __file__
as test_support_file
35 def openfile(filename
):
36 path
= os
.path
.join(os
.path
.dirname(test_support_file
), 'data', filename
)
42 class TestEmailBase(unittest
.TestCase
):
43 def _msgobj(self
, filename
):
44 fp
= openfile(filename
)
46 msg
= email
.message_from_file(fp
)
53 # Test various aspects of the Message class's API
54 class TestMessageAPI(TestEmailBase
):
55 def test_get_all(self
):
57 msg
= self
._msgobj
('msg_20.txt')
58 eq(msg
.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
59 eq(msg
.get_all('xx', 'n/a'), 'n/a')
61 def test_get_charsets(self
):
64 msg
= self
._msgobj
('msg_08.txt')
65 charsets
= msg
.get_charsets()
66 eq(charsets
, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
68 msg
= self
._msgobj
('msg_09.txt')
69 charsets
= msg
.get_charsets('dingbat')
70 eq(charsets
, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
73 msg
= self
._msgobj
('msg_12.txt')
74 charsets
= msg
.get_charsets()
75 eq(charsets
, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
76 'iso-8859-3', 'us-ascii', 'koi8-r'])
78 def test_get_filename(self
):
81 msg
= self
._msgobj
('msg_04.txt')
82 filenames
= [p
.get_filename() for p
in msg
.get_payload()]
83 eq(filenames
, ['msg.txt', 'msg.txt'])
85 msg
= self
._msgobj
('msg_07.txt')
86 subpart
= msg
.get_payload(1)
87 eq(subpart
.get_filename(), 'dingusfish.gif')
89 def test_get_boundary(self
):
91 msg
= self
._msgobj
('msg_07.txt')
93 eq(msg
.get_boundary(), 'BOUNDARY')
95 def test_set_boundary(self
):
97 # This one has no existing boundary parameter, but the Content-Type:
98 # header appears fifth.
99 msg
= self
._msgobj
('msg_01.txt')
100 msg
.set_boundary('BOUNDARY')
101 header
, value
= msg
.items()[4]
102 eq(header
.lower(), 'content-type')
103 eq(value
, 'text/plain; charset=us-ascii; boundary="BOUNDARY"')
104 # This one has a Content-Type: header, with a boundary, stuck in the
105 # middle of its headers. Make sure the order is preserved; it should
107 msg
= self
._msgobj
('msg_04.txt')
108 msg
.set_boundary('BOUNDARY')
109 header
, value
= msg
.items()[4]
110 eq(header
.lower(), 'content-type')
111 eq(value
, 'multipart/mixed; boundary="BOUNDARY"')
112 # And this one has no Content-Type: header at all.
113 msg
= self
._msgobj
('msg_03.txt')
114 self
.assertRaises(Errors
.HeaderParseError
,
115 msg
.set_boundary
, 'BOUNDARY')
117 def test_get_decoded_payload(self
):
118 eq
= self
.assertEqual
119 msg
= self
._msgobj
('msg_10.txt')
120 # The outer message is a multipart
121 eq(msg
.get_payload(decode
=1), None)
122 # Subpart 1 is 7bit encoded
123 eq(msg
.get_payload(0).get_payload(decode
=1),
124 'This is a 7bit encoded message.\n')
125 # Subpart 2 is quopri
126 eq(msg
.get_payload(1).get_payload(decode
=1),
127 '\xa1This is a Quoted Printable encoded message!\n')
128 # Subpart 3 is base64
129 eq(msg
.get_payload(2).get_payload(decode
=1),
130 'This is a Base64 encoded message.')
131 # Subpart 4 has no Content-Transfer-Encoding: header.
132 eq(msg
.get_payload(3).get_payload(decode
=1),
133 'This has no Content-Transfer-Encoding: header.\n')
135 def test_decoded_generator(self
):
136 eq
= self
.assertEqual
137 msg
= self
._msgobj
('msg_07.txt')
138 fp
= openfile('msg_17.txt')
144 g
= DecodedGenerator(s
)
146 eq(s
.getvalue(), text
)
148 def test__contains__(self
):
152 # Check for case insensitivity
153 self
.failUnless('from' in msg
)
154 self
.failUnless('From' in msg
)
155 self
.failUnless('FROM' in msg
)
156 self
.failUnless('to' in msg
)
157 self
.failUnless('To' in msg
)
158 self
.failUnless('TO' in msg
)
160 def test_as_string(self
):
161 eq
= self
.assertEqual
162 msg
= self
._msgobj
('msg_01.txt')
163 fp
= openfile('msg_01.txt')
168 eq(text
, msg
.as_string())
170 lines
= fullrepr
.split('\n')
171 self
.failUnless(lines
[0].startswith('From '))
172 eq(text
, NL
.join(lines
[1:]))
174 def test_bad_param(self
):
175 msg
= email
.message_from_string("Content-Type: blarg; baz; boo\n")
176 self
.assertEqual(msg
.get_param('baz'), '')
178 def test_missing_filename(self
):
179 msg
= email
.message_from_string("From: foo\n")
180 self
.assertEqual(msg
.get_filename(), None)
182 def test_bogus_filename(self
):
183 msg
= email
.message_from_string(
184 "Content-Disposition: blarg; filename\n")
185 self
.assertEqual(msg
.get_filename(), '')
187 def test_missing_boundary(self
):
188 msg
= email
.message_from_string("From: foo\n")
189 self
.assertEqual(msg
.get_boundary(), None)
191 def test_get_params(self
):
192 eq
= self
.assertEqual
193 msg
= email
.message_from_string(
194 'X-Header: foo=one; bar=two; baz=three\n')
195 eq(msg
.get_params(header
='x-header'),
196 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
197 msg
= email
.message_from_string(
198 'X-Header: foo; bar=one; baz=two\n')
199 eq(msg
.get_params(header
='x-header'),
200 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
201 eq(msg
.get_params(), None)
202 msg
= email
.message_from_string(
203 'X-Header: foo; bar="one"; baz=two\n')
204 eq(msg
.get_params(header
='x-header'),
205 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
207 def test_get_param(self
):
208 eq
= self
.assertEqual
209 msg
= email
.message_from_string(
210 "X-Header: foo=one; bar=two; baz=three\n")
211 eq(msg
.get_param('bar', header
='x-header'), 'two')
212 eq(msg
.get_param('quuz', header
='x-header'), None)
213 eq(msg
.get_param('quuz'), None)
214 msg
= email
.message_from_string(
215 'X-Header: foo; bar="one"; baz=two\n')
216 eq(msg
.get_param('foo', header
='x-header'), '')
217 eq(msg
.get_param('bar', header
='x-header'), 'one')
218 eq(msg
.get_param('baz', header
='x-header'), 'two')
220 def test_get_param_funky_continuation_lines(self
):
221 msg
= self
._msgobj
('msg_22.txt')
222 self
.assertEqual(msg
.get_payload(1).get_param('name'), 'wibble.JPG')
224 def test_has_key(self
):
225 msg
= email
.message_from_string('Header: exists')
226 self
.failUnless(msg
.has_key('header'))
227 self
.failUnless(msg
.has_key('Header'))
228 self
.failUnless(msg
.has_key('HEADER'))
229 self
.failIf(msg
.has_key('headeri'))
233 # Test the email.Encoders module
234 class TestEncoders(unittest
.TestCase
):
235 def test_encode_noop(self
):
236 eq
= self
.assertEqual
237 msg
= MIMEText('hello world', _encoder
=Encoders
.encode_noop
)
238 eq(msg
.get_payload(), 'hello world\n')
239 eq(msg
['content-transfer-encoding'], None)
241 def test_encode_7bit(self
):
242 eq
= self
.assertEqual
243 msg
= MIMEText('hello world', _encoder
=Encoders
.encode_7or8bit
)
244 eq(msg
.get_payload(), 'hello world\n')
245 eq(msg
['content-transfer-encoding'], '7bit')
246 msg
= MIMEText('hello \x7f world', _encoder
=Encoders
.encode_7or8bit
)
247 eq(msg
.get_payload(), 'hello \x7f world\n')
248 eq(msg
['content-transfer-encoding'], '7bit')
250 def test_encode_8bit(self
):
251 eq
= self
.assertEqual
252 msg
= MIMEText('hello \x80 world', _encoder
=Encoders
.encode_7or8bit
)
253 eq(msg
.get_payload(), 'hello \x80 world\n')
254 eq(msg
['content-transfer-encoding'], '8bit')
256 def test_encode_base64(self
):
257 eq
= self
.assertEqual
258 msg
= MIMEText('hello world', _encoder
=Encoders
.encode_base64
)
259 eq(msg
.get_payload(), 'aGVsbG8gd29ybGQK\n')
260 eq(msg
['content-transfer-encoding'], 'base64')
262 def test_encode_quoted_printable(self
):
263 eq
= self
.assertEqual
264 msg
= MIMEText('hello world', _encoder
=Encoders
.encode_quopri
)
265 eq(msg
.get_payload(), 'hello=20world\n')
266 eq(msg
['content-transfer-encoding'], 'quoted-printable')
270 # Test long header wrapping
271 class TestLongHeaders(unittest
.TestCase
):
272 def test_header_splitter(self
):
274 # It'd be great if we could use add_header() here, but that doesn't
275 # guarantee an order of the parameters.
276 msg
['X-Foobar-Spoink-Defrobnit'] = (
277 'wasnipoop; giraffes="very-long-necked-animals"; '
278 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
282 self
.assertEqual(sfp
.getvalue(), openfile('msg_18.txt').read())
284 def test_no_semis_header_splitter(self
):
286 msg
['From'] = 'test@dom.ain'
289 refparts
.append('<%d@dom.ain>' % i
)
290 msg
['References'] = SPACE
.join(refparts
)
291 msg
.set_payload('Test')
295 self
.assertEqual(sfp
.getvalue(), """\
297 References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
298 \t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
302 def test_no_split_long_header(self
):
304 msg
['From'] = 'test@dom.ain'
306 msg
['References'] = 'x' * 80
307 msg
.set_payload('Test')
311 self
.assertEqual(sfp
.getvalue(), """\
313 References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
319 # Test mangling of "From " lines in the body of a message
320 class TestFromMangling(unittest
.TestCase
):
323 self
.msg
['From'] = 'aaa@bbb.org'
324 self
.msg
.add_payload("""\
325 From the desk of A.A.A.:
329 def test_mangled_from(self
):
331 g
= Generator(s
, mangle_from_
=1)
333 self
.assertEqual(s
.getvalue(), """\
336 >From the desk of A.A.A.:
340 def test_dont_mangle_from(self
):
342 g
= Generator(s
, mangle_from_
=0)
344 self
.assertEqual(s
.getvalue(), """\
347 From the desk of A.A.A.:
353 # Test the basic MIMEAudio class
354 class TestMIMEAudio(unittest
.TestCase
):
356 # In Python, audiotest.au lives in Lib/test not Lib/test/data
357 fp
= open(findfile('audiotest.au'))
359 self
._audiodata
= fp
.read()
362 self
._au
= MIMEAudio(self
._audiodata
)
364 def test_guess_minor_type(self
):
365 self
.assertEqual(self
._au
.get_type(), 'audio/basic')
367 def test_encoding(self
):
368 payload
= self
._au
.get_payload()
369 self
.assertEqual(base64
.decodestring(payload
), self
._audiodata
)
371 def checkSetMinor(self
):
372 au
= MIMEAudio(self
._audiodata
, 'fish')
373 self
.assertEqual(im
.get_type(), 'audio/fish')
375 def test_custom_encoder(self
):
376 eq
= self
.assertEqual
378 orig
= msg
.get_payload()
380 msg
['Content-Transfer-Encoding'] = 'broken64'
381 au
= MIMEAudio(self
._audiodata
, _encoder
=encoder
)
382 eq(au
.get_payload(), 0)
383 eq(au
['content-transfer-encoding'], 'broken64')
385 def test_add_header(self
):
386 eq
= self
.assertEqual
387 unless
= self
.failUnless
388 self
._au
.add_header('Content-Disposition', 'attachment',
389 filename
='audiotest.au')
390 eq(self
._au
['content-disposition'],
391 'attachment; filename="audiotest.au"')
392 eq(self
._au
.get_params(header
='content-disposition'),
393 [('attachment', ''), ('filename', 'audiotest.au')])
394 eq(self
._au
.get_param('filename', header
='content-disposition'),
397 eq(self
._au
.get_param('attachment', header
='content-disposition'), '')
398 unless(self
._au
.get_param('foo', failobj
=missing
,
399 header
='content-disposition') is missing
)
400 # Try some missing stuff
401 unless(self
._au
.get_param('foobar', missing
) is missing
)
402 unless(self
._au
.get_param('attachment', missing
,
403 header
='foobar') is missing
)
407 # Test the basic MIMEImage class
408 class TestMIMEImage(unittest
.TestCase
):
410 fp
= openfile('PyBanner048.gif')
412 self
._imgdata
= fp
.read()
415 self
._im
= MIMEImage(self
._imgdata
)
417 def test_guess_minor_type(self
):
418 self
.assertEqual(self
._im
.get_type(), 'image/gif')
420 def test_encoding(self
):
421 payload
= self
._im
.get_payload()
422 self
.assertEqual(base64
.decodestring(payload
), self
._imgdata
)
424 def checkSetMinor(self
):
425 im
= MIMEImage(self
._imgdata
, 'fish')
426 self
.assertEqual(im
.get_type(), 'image/fish')
428 def test_custom_encoder(self
):
429 eq
= self
.assertEqual
431 orig
= msg
.get_payload()
433 msg
['Content-Transfer-Encoding'] = 'broken64'
434 im
= MIMEImage(self
._imgdata
, _encoder
=encoder
)
435 eq(im
.get_payload(), 0)
436 eq(im
['content-transfer-encoding'], 'broken64')
438 def test_add_header(self
):
439 eq
= self
.assertEqual
440 unless
= self
.failUnless
441 self
._im
.add_header('Content-Disposition', 'attachment',
442 filename
='dingusfish.gif')
443 eq(self
._im
['content-disposition'],
444 'attachment; filename="dingusfish.gif"')
445 eq(self
._im
.get_params(header
='content-disposition'),
446 [('attachment', ''), ('filename', 'dingusfish.gif')])
447 eq(self
._im
.get_param('filename', header
='content-disposition'),
450 eq(self
._im
.get_param('attachment', header
='content-disposition'), '')
451 unless(self
._im
.get_param('foo', failobj
=missing
,
452 header
='content-disposition') is missing
)
453 # Try some missing stuff
454 unless(self
._im
.get_param('foobar', missing
) is missing
)
455 unless(self
._im
.get_param('attachment', missing
,
456 header
='foobar') is missing
)
460 # Test the basic MIMEText class
461 class TestMIMEText(unittest
.TestCase
):
463 self
._msg
= MIMEText('hello there')
465 def test_types(self
):
466 eq
= self
.assertEqual
467 unless
= self
.failUnless
468 eq(self
._msg
.get_type(), 'text/plain')
469 eq(self
._msg
.get_param('charset'), 'us-ascii')
471 unless(self
._msg
.get_param('foobar', missing
) is missing
)
472 unless(self
._msg
.get_param('charset', missing
, header
='foobar')
475 def test_payload(self
):
476 self
.assertEqual(self
._msg
.get_payload(), 'hello there\n')
477 self
.failUnless(not self
._msg
.is_multipart())
481 # Test a more complicated multipart/mixed type message
482 class TestMultipartMixed(unittest
.TestCase
):
484 fp
= openfile('PyBanner048.gif')
490 container
= MIMEBase('multipart', 'mixed', boundary
='BOUNDARY')
491 image
= MIMEImage(data
, name
='dingusfish.gif')
492 image
.add_header('content-disposition', 'attachment',
493 filename
='dingusfish.gif')
494 intro
= MIMEText('''\
497 This is the dingus fish.
499 container
.add_payload(intro
)
500 container
.add_payload(image
)
501 container
['From'] = 'Barry <barry@digicool.com>'
502 container
['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
503 container
['Subject'] = 'Here is your dingus fish'
505 now
= 987809702.54848599
506 timetuple
= time
.localtime(now
)
507 if timetuple
[-1] == 0:
508 tzsecs
= time
.timezone
510 tzsecs
= time
.altzone
515 tzoffset
= ' %s%04d' % (sign
, tzsecs
/ 36)
516 container
['Date'] = time
.strftime(
517 '%a, %d %b %Y %H:%M:%S',
518 time
.localtime(now
)) + tzoffset
519 self
._msg
= container
523 def test_hierarchy(self
):
525 eq
= self
.assertEqual
526 unless
= self
.failUnless
527 raises
= self
.assertRaises
530 unless(m
.is_multipart())
531 eq(m
.get_type(), 'multipart/mixed')
532 eq(len(m
.get_payload()), 2)
533 raises(IndexError, m
.get_payload
, 2)
534 m0
= m
.get_payload(0)
535 m1
= m
.get_payload(1)
536 unless(m0
is self
._txt
)
537 unless(m1
is self
._im
)
538 eq(m
.get_payload(), [m0
, m1
])
539 unless(not m0
.is_multipart())
540 unless(not m1
.is_multipart())
544 # Test some badly formatted messages
545 class TestNonConformant(TestEmailBase
):
546 def test_parse_missing_minor_type(self
):
547 eq
= self
.assertEqual
548 msg
= self
._msgobj
('msg_14.txt')
549 eq(msg
.get_type(), 'text')
550 eq(msg
.get_main_type(), 'text')
551 self
.failUnless(msg
.get_subtype() is None)
553 def test_bogus_boundary(self
):
554 fp
= openfile('msg_15.txt')
560 # Note, under a future non-strict parsing mode, this would parse the
561 # message into the intended message tree.
562 self
.assertRaises(Errors
.BoundaryError
, p
.parsestr
, data
)
566 # Test RFC 2047 header encoding and decoding
567 class TestRFC2047(unittest
.TestCase
):
568 def test_iso_8859_1(self
):
569 eq
= self
.assertEqual
570 s
= '=?iso-8859-1?q?this=20is=20some=20text?='
571 eq(Utils
.decode(s
), 'this is some text')
572 s
= '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
573 eq(Utils
.decode(s
), u
'Keld_J\xf8rn_Simonsen')
574 s
= '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
575 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
576 eq(Utils
.decode(s
), 'If you can read this you understand the example.')
577 s
= '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
579 u
'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
580 s
= '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
581 eq(Utils
.decode(s
), u
'this is some text')
583 def test_encode_header(self
):
584 eq
= self
.assertEqual
585 s
= 'this is some text'
586 eq(Utils
.encode(s
), '=?iso-8859-1?q?this=20is=20some=20text?=')
587 s
= 'Keld_J\xf8rn_Simonsen'
588 eq(Utils
.encode(s
), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
589 s1
= 'If you can read this yo'
590 s2
= 'u understand the example.'
591 eq(Utils
.encode(s1
, encoding
='b'),
592 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
593 eq(Utils
.encode(s2
, charset
='iso-8859-2', encoding
='b'),
594 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
598 # Test the MIMEMessage class
599 class TestMIMEMessage(TestEmailBase
):
601 fp
= openfile('msg_11.txt')
602 self
._text
= fp
.read()
605 def test_type_error(self
):
606 self
.assertRaises(TypeError, MIMEMessage
, 'a plain string')
608 def test_valid_argument(self
):
609 eq
= self
.assertEqual
610 subject
= 'A sub-message'
612 m
['Subject'] = subject
614 eq(r
.get_type(), 'message/rfc822')
615 self
.failUnless(r
.get_payload() is m
)
616 eq(r
.get_payload()['subject'], subject
)
618 def test_generate(self
):
619 # First craft the message to be encapsulated
621 m
['Subject'] = 'An enclosed message'
622 m
.add_payload('Here is the body of the message.\n')
624 r
['Subject'] = 'The enclosing message'
628 self
.assertEqual(s
.getvalue(), """\
629 Content-Type: message/rfc822
631 Subject: The enclosing message
633 Subject: An enclosed message
635 Here is the body of the message.
638 def test_parse_message_rfc822(self
):
639 eq
= self
.assertEqual
640 msg
= self
._msgobj
('msg_11.txt')
641 eq(msg
.get_type(), 'message/rfc822')
642 eq(len(msg
.get_payload()), 1)
643 submsg
= msg
.get_payload()
644 self
.failUnless(isinstance(submsg
, Message
))
645 eq(submsg
['subject'], 'An enclosed message')
646 eq(submsg
.get_payload(), 'Here is the body of the message.\n')
649 eq
= self
.assertEqual
650 unless
= self
.failUnless
651 # msg 16 is a Delivery Status Notification, see RFC XXXX
652 msg
= self
._msgobj
('msg_16.txt')
653 eq(msg
.get_type(), 'multipart/report')
654 unless(msg
.is_multipart())
655 eq(len(msg
.get_payload()), 3)
656 # Subpart 1 is a text/plain, human readable section
657 subpart
= msg
.get_payload(0)
658 eq(subpart
.get_type(), 'text/plain')
659 eq(subpart
.get_payload(), """\
660 This report relates to a message you sent with the following header fields:
662 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
663 Date: Sun, 23 Sep 2001 20:10:55 -0700
664 From: "Ian T. Henry" <henryi@oxy.edu>
665 To: SoCal Raves <scr@socal-raves.org>
666 Subject: [scr] yeah for Ians!!
668 Your message cannot be delivered to the following recipients:
670 Recipient address: jangel1@cougar.noc.ucla.edu
671 Reason: recipient reached disk quota
674 # Subpart 2 contains the machine parsable DSN information. It
675 # consists of two blocks of headers, represented by two nested Message
677 subpart
= msg
.get_payload(1)
678 eq(subpart
.get_type(), 'message/delivery-status')
679 eq(len(subpart
.get_payload()), 2)
680 # message/delivery-status should treat each block as a bunch of
681 # headers, i.e. a bunch of Message objects.
682 dsn1
= subpart
.get_payload(0)
683 unless(isinstance(dsn1
, Message
))
684 eq(dsn1
['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
685 eq(dsn1
.get_param('dns', header
='reporting-mta'), '')
686 # Try a missing one <wink>
687 eq(dsn1
.get_param('nsd', header
='reporting-mta'), None)
688 dsn2
= subpart
.get_payload(1)
689 unless(isinstance(dsn2
, Message
))
690 eq(dsn2
['action'], 'failed')
691 eq(dsn2
.get_params(header
='original-recipient'),
692 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
693 eq(dsn2
.get_param('rfc822', header
='final-recipient'), '')
694 # Subpart 3 is the original message
695 subpart
= msg
.get_payload(2)
696 eq(subpart
.get_type(), 'message/rfc822')
697 subsubpart
= subpart
.get_payload()
698 unless(isinstance(subsubpart
, Message
))
699 eq(subsubpart
.get_type(), 'text/plain')
700 eq(subsubpart
['message-id'],
701 '<002001c144a6$8752e060$56104586@oxy.edu>')
703 def test_epilogue(self
):
704 fp
= openfile('msg_21.txt')
710 msg
['From'] = 'aperson@dom.ain'
711 msg
['To'] = 'bperson@dom.ain'
712 msg
['Subject'] = 'Test'
713 msg
.preamble
= 'MIME message\n'
714 msg
.epilogue
= 'End of MIME message\n'
715 msg1
= MIMEText('One')
716 msg2
= MIMEText('Two')
717 msg
.add_header('Content-Type', 'multipart/mixed', boundary
='BOUNDARY')
718 msg
.add_payload(msg1
)
719 msg
.add_payload(msg2
)
723 self
.assertEqual(sfp
.getvalue(), text
)
727 # A general test of parser->model->generator idempotency. IOW, read a message
728 # in, parse it into a message object tree, then without touching the tree,
729 # regenerate the plain text. The original text and the transformed text
730 # should be identical. Note: that we ignore the Unix-From since that may
731 # contain a changed date.
732 class TestIdempotent(unittest
.TestCase
):
733 def _msgobj(self
, filename
):
734 fp
= openfile(filename
)
739 msg
= email
.message_from_string(data
)
742 def _idempotent(self
, msg
, text
):
743 eq
= self
.assertEquals
745 g
= Generator(s
, maxheaderlen
=0)
747 eq(text
, s
.getvalue())
749 def test_parse_text_message(self
):
750 eq
= self
.assertEquals
751 msg
, text
= self
._msgobj
('msg_01.txt')
752 eq(msg
.get_type(), 'text/plain')
753 eq(msg
.get_main_type(), 'text')
754 eq(msg
.get_subtype(), 'plain')
755 eq(msg
.get_params()[1], ('charset', 'us-ascii'))
756 eq(msg
.get_param('charset'), 'us-ascii')
757 eq(msg
.preamble
, None)
758 eq(msg
.epilogue
, None)
759 self
._idempotent
(msg
, text
)
761 def test_parse_untyped_message(self
):
762 eq
= self
.assertEquals
763 msg
, text
= self
._msgobj
('msg_03.txt')
764 eq(msg
.get_type(), None)
765 eq(msg
.get_params(), None)
766 eq(msg
.get_param('charset'), None)
767 self
._idempotent
(msg
, text
)
769 def test_simple_multipart(self
):
770 msg
, text
= self
._msgobj
('msg_04.txt')
771 self
._idempotent
(msg
, text
)
773 def test_MIME_digest(self
):
774 msg
, text
= self
._msgobj
('msg_02.txt')
775 self
._idempotent
(msg
, text
)
777 def test_mixed_with_image(self
):
778 msg
, text
= self
._msgobj
('msg_06.txt')
779 self
._idempotent
(msg
, text
)
781 def test_multipart_report(self
):
782 msg
, text
= self
._msgobj
('msg_05.txt')
783 self
._idempotent
(msg
, text
)
786 msg
, text
= self
._msgobj
('msg_16.txt')
787 self
._idempotent
(msg
, text
)
789 def test_preamble_epilogue(self
):
790 msg
, text
= self
._msgobj
('msg_21.txt')
791 self
._idempotent
(msg
, text
)
793 def test_content_type(self
):
794 eq
= self
.assertEquals
795 # Get a message object and reset the seek pointer for other tests
796 msg
, text
= self
._msgobj
('msg_05.txt')
797 eq(msg
.get_type(), 'multipart/report')
798 # Test the Content-Type: parameters
800 for pk
, pv
in msg
.get_params():
802 eq(params
['report-type'], 'delivery-status')
803 eq(params
['boundary'], 'D1690A7AC1.996856090/mail.example.com')
804 eq(msg
.preamble
, 'This is a MIME-encapsulated message.\n\n')
805 eq(msg
.epilogue
, '\n\n')
806 eq(len(msg
.get_payload()), 3)
807 # Make sure the subparts are what we expect
808 msg1
= msg
.get_payload(0)
809 eq(msg1
.get_type(), 'text/plain')
810 eq(msg1
.get_payload(), 'Yadda yadda yadda\n')
811 msg2
= msg
.get_payload(1)
812 eq(msg2
.get_type(), None)
813 eq(msg2
.get_payload(), 'Yadda yadda yadda\n')
814 msg3
= msg
.get_payload(2)
815 eq(msg3
.get_type(), 'message/rfc822')
816 self
.failUnless(isinstance(msg3
, Message
))
817 msg4
= msg3
.get_payload()
818 self
.failUnless(isinstance(msg4
, Message
))
819 eq(msg4
.get_payload(), 'Yadda yadda yadda\n')
821 def test_parser(self
):
822 eq
= self
.assertEquals
823 msg
, text
= self
._msgobj
('msg_06.txt')
824 # Check some of the outer headers
825 eq(msg
.get_type(), 'message/rfc822')
826 # Make sure there's exactly one thing in the payload and that's a
827 # sub-Message object of type text/plain
828 msg1
= msg
.get_payload()
829 self
.failUnless(isinstance(msg1
, Message
))
830 eq(msg1
.get_type(), 'text/plain')
831 self
.failUnless(isinstance(msg1
.get_payload(), StringType
))
832 eq(msg1
.get_payload(), '\n')
836 # Test various other bits of the package's functionality
837 class TestMiscellaneous(unittest
.TestCase
):
838 def test_message_from_string(self
):
839 fp
= openfile('msg_01.txt')
844 msg
= email
.message_from_string(text
)
846 # Don't wrap/continue long headers since we're trying to test
848 g
= Generator(s
, maxheaderlen
=0)
850 self
.assertEqual(text
, s
.getvalue())
852 def test_message_from_file(self
):
853 fp
= openfile('msg_01.txt')
857 msg
= email
.message_from_file(fp
)
859 # Don't wrap/continue long headers since we're trying to test
861 g
= Generator(s
, maxheaderlen
=0)
863 self
.assertEqual(text
, s
.getvalue())
867 def test_message_from_string_with_class(self
):
868 unless
= self
.failUnless
869 fp
= openfile('msg_01.txt')
875 class MyMessage(Message
):
878 msg
= email
.message_from_string(text
, MyMessage
)
879 unless(isinstance(msg
, MyMessage
))
880 # Try something more complicated
881 fp
= openfile('msg_02.txt')
886 msg
= email
.message_from_string(text
, MyMessage
)
887 for subpart
in msg
.walk():
888 unless(isinstance(subpart
, MyMessage
))
890 def test_message_from_file_with_class(self
):
891 unless
= self
.failUnless
893 class MyMessage(Message
):
896 fp
= openfile('msg_01.txt')
898 msg
= email
.message_from_file(fp
, MyMessage
)
901 unless(isinstance(msg
, MyMessage
))
902 # Try something more complicated
903 fp
= openfile('msg_02.txt')
905 msg
= email
.message_from_file(fp
, MyMessage
)
908 for subpart
in msg
.walk():
909 unless(isinstance(subpart
, MyMessage
))
911 def test__all__(self
):
912 module
= __import__('email')
915 self
.assertEqual(all
, ['Encoders', 'Errors', 'Generator', 'Iterators',
916 'MIMEAudio', 'MIMEBase', 'MIMEImage',
917 'MIMEMessage', 'MIMEText', 'Message', 'Parser',
919 'message_from_file', 'message_from_string'])
921 def test_formatdate(self
):
922 now
= 1005327232.109884
923 gm_epoch
= time
.gmtime(0)[0:3]
924 loc_epoch
= time
.localtime(0)[0:3]
925 # When does the epoch start?
926 if gm_epoch
== (1970, 1, 1):
927 # traditional Unix epoch
928 matchdate
= 'Fri, 09 Nov 2001 17:33:52 -0000'
929 elif loc_epoch
== (1904, 1, 1):
931 matchdate
= 'Sat, 09 Nov 1935 16:33:52 -0000'
933 matchdate
= "I don't understand your epoch"
934 gdate
= Utils
.formatdate(now
)
935 self
.assertEqual(gdate
, matchdate
)
937 def test_formatdate_localtime(self
):
938 now
= 1005327232.109884
939 ldate
= Utils
.formatdate(now
, localtime
=1)
940 zone
= ldate
.split()[5]
941 offset
= int(zone
[1:3]) * 3600 + int(zone
[-2:]) * 60
942 # Remember offset is in seconds west of UTC, but the timezone is in
943 # minutes east of UTC, so the signs differ.
946 if time
.daylight
and time
.localtime(now
)[-1]:
950 self
.assertEqual(offset
, toff
)
952 def test_parsedate_none(self
):
953 self
.assertEqual(Utils
.parsedate(''), None)
955 def test_parseaddr_empty(self
):
956 self
.assertEqual(Utils
.parseaddr('<>'), ('', ''))
957 self
.assertEqual(Utils
.dump_address_pair(Utils
.parseaddr('<>')), '')
961 # Test the iterator/generators
962 class TestIterators(TestEmailBase
):
963 def test_body_line_iterator(self
):
964 eq
= self
.assertEqual
965 # First a simple non-multipart message
966 msg
= self
._msgobj
('msg_01.txt')
967 it
= Iterators
.body_line_iterator(msg
)
970 eq(EMPTYSTRING
.join(lines
), msg
.get_payload())
971 # Now a more complicated multipart
972 msg
= self
._msgobj
('msg_02.txt')
973 it
= Iterators
.body_line_iterator(msg
)
976 eq(EMPTYSTRING
.join(lines
), openfile('msg_19.txt').read())
978 def test_typed_subpart_iterator(self
):
979 eq
= self
.assertEqual
980 msg
= self
._msgobj
('msg_04.txt')
981 it
= Iterators
.typed_subpart_iterator(msg
, 'text')
982 lines
= [subpart
.get_payload() for subpart
in it
]
984 eq(EMPTYSTRING
.join(lines
), """\
985 a simple kind of mirror
986 to reflect upon our own
987 a simple kind of mirror
988 to reflect upon our own
991 def test_typed_subpart_iterator_default_type(self
):
992 eq
= self
.assertEqual
993 msg
= self
._msgobj
('msg_03.txt')
994 it
= Iterators
.typed_subpart_iterator(msg
, 'text', 'plain')
999 lines
.append(subpart
.get_payload())
1001 eq(EMPTYSTRING
.join(lines
), """\
1005 Do you like this message?
1011 class TestParsers(unittest
.TestCase
):
1012 def test_header_parser(self
):
1013 eq
= self
.assertEqual
1014 # Parse only the headers of a complex multipart MIME document
1016 fp
= openfile('msg_02.txt')
1018 eq(msg
['from'], 'ppp-request@zzz.org')
1019 eq(msg
['to'], 'ppp@zzz.org')
1020 eq(msg
.get_type(), 'multipart/mixed')
1021 eq(msg
.is_multipart(), 0)
1022 self
.failUnless(isinstance(msg
.get_payload(), StringType
))
1027 suite
= unittest
.TestSuite()
1028 suite
.addTest(unittest
.makeSuite(TestMessageAPI
))
1029 suite
.addTest(unittest
.makeSuite(TestEncoders
))
1030 suite
.addTest(unittest
.makeSuite(TestLongHeaders
))
1031 suite
.addTest(unittest
.makeSuite(TestFromMangling
))
1032 suite
.addTest(unittest
.makeSuite(TestMIMEAudio
))
1033 suite
.addTest(unittest
.makeSuite(TestMIMEImage
))
1034 suite
.addTest(unittest
.makeSuite(TestMIMEText
))
1035 suite
.addTest(unittest
.makeSuite(TestMultipartMixed
))
1036 suite
.addTest(unittest
.makeSuite(TestNonConformant
))
1037 suite
.addTest(unittest
.makeSuite(TestRFC2047
))
1038 suite
.addTest(unittest
.makeSuite(TestMIMEMessage
))
1039 suite
.addTest(unittest
.makeSuite(TestIdempotent
))
1040 suite
.addTest(unittest
.makeSuite(TestMiscellaneous
))
1041 suite
.addTest(unittest
.makeSuite(TestIterators
))
1042 suite
.addTest(unittest
.makeSuite(TestParsers
))
1048 from test_support
import run_suite
1051 if __name__
== '__main__':