blob: 2c91b36b015911e840527c344cb982b94c5041a0 [file] [log] [blame]
Barry Warsaw763af412002-01-27 06:48:47 +00001# Copyright (C) 2001,2002 Python Software Foundation
Barry Warsaw41075852001-09-23 03:18:13 +00002# email package unit tests
3
Barry Warsaw409a4c02002-04-10 21:01:31 +00004import sys
Barry Warsaw41075852001-09-23 03:18:13 +00005import os
6import time
7import unittest
8import base64
9from cStringIO import StringIO
Barry Warsaw2c685062002-06-02 19:09:27 +000010from types import StringType, ListType
Barry Warsaw409a4c02002-04-10 21:01:31 +000011import warnings
Barry Warsaw41075852001-09-23 03:18:13 +000012
13import email
14
Barry Warsaw409a4c02002-04-10 21:01:31 +000015from email.Charset import Charset
16from email.Header import Header, decode_header
Barry Warsawbf7a59d2001-10-11 15:44:50 +000017from email.Parser import Parser, HeaderParser
Barry Warsaw41075852001-09-23 03:18:13 +000018from email.Generator import Generator, DecodedGenerator
19from email.Message import Message
Barry Warsawfee435a2001-10-09 19:23:57 +000020from email.MIMEAudio import MIMEAudio
Barry Warsaw65279d02001-09-26 05:47:08 +000021from email.MIMEText import MIMEText
22from email.MIMEImage import MIMEImage
Barry Warsaw41075852001-09-23 03:18:13 +000023from email.MIMEBase import MIMEBase
Barry Warsaw65279d02001-09-26 05:47:08 +000024from email.MIMEMessage import MIMEMessage
Barry Warsaw41075852001-09-23 03:18:13 +000025from email import Utils
26from email import Errors
27from email import Encoders
28from email import Iterators
Barry Warsaw409a4c02002-04-10 21:01:31 +000029from email import base64MIME
30from email import quopriMIME
Barry Warsaw41075852001-09-23 03:18:13 +000031
Barry Warsawc9ad32c2002-04-15 22:14:06 +000032import test_support
Guido van Rossum78f0dd32001-12-07 21:07:08 +000033from test_support import findfile, __file__ as test_support_file
Barry Warsaw41075852001-09-23 03:18:13 +000034
Barry Warsawc9ad32c2002-04-15 22:14:06 +000035
Barry Warsaw41075852001-09-23 03:18:13 +000036NL = '\n'
37EMPTYSTRING = ''
Barry Warsaw07227d12001-10-17 20:52:26 +000038SPACE = ' '
Barry Warsaw41075852001-09-23 03:18:13 +000039
Barry Warsaw409a4c02002-04-10 21:01:31 +000040# We don't care about DeprecationWarnings
41warnings.filterwarnings('ignore', '', DeprecationWarning, __name__)
42
Barry Warsaw41075852001-09-23 03:18:13 +000043
Barry Warsaw08a534d2001-10-04 17:58:50 +000044
Barry Warsaw41075852001-09-23 03:18:13 +000045def openfile(filename):
Guido van Rossum78f0dd32001-12-07 21:07:08 +000046 path = os.path.join(os.path.dirname(test_support_file), 'data', filename)
Barry Warsaw41075852001-09-23 03:18:13 +000047 return open(path)
48
49
Barry Warsaw08a534d2001-10-04 17:58:50 +000050
Barry Warsaw41075852001-09-23 03:18:13 +000051# Base test class
52class TestEmailBase(unittest.TestCase):
53 def _msgobj(self, filename):
Barry Warsaw409a4c02002-04-10 21:01:31 +000054 fp = openfile(findfile(filename))
Barry Warsaw41075852001-09-23 03:18:13 +000055 try:
Barry Warsaw65279d02001-09-26 05:47:08 +000056 msg = email.message_from_file(fp)
Barry Warsaw41075852001-09-23 03:18:13 +000057 finally:
58 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +000059 return msg
Barry Warsaw41075852001-09-23 03:18:13 +000060
61
Barry Warsaw08a534d2001-10-04 17:58:50 +000062
Barry Warsaw41075852001-09-23 03:18:13 +000063# Test various aspects of the Message class's API
64class TestMessageAPI(TestEmailBase):
Barry Warsaw2f6a0b02001-10-09 15:49:35 +000065 def test_get_all(self):
66 eq = self.assertEqual
67 msg = self._msgobj('msg_20.txt')
68 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
69 eq(msg.get_all('xx', 'n/a'), 'n/a')
70
Barry Warsaw409a4c02002-04-10 21:01:31 +000071 def test_getset_charset(self):
72 eq = self.assertEqual
73 msg = Message()
74 eq(msg.get_charset(), None)
75 charset = Charset('iso-8859-1')
76 msg.set_charset(charset)
77 eq(msg['mime-version'], '1.0')
78 eq(msg.get_type(), 'text/plain')
79 eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
80 eq(msg.get_param('charset'), 'iso-8859-1')
81 eq(msg['content-transfer-encoding'], 'quoted-printable')
82 eq(msg.get_charset().input_charset, 'iso-8859-1')
83 # Remove the charset
84 msg.set_charset(None)
85 eq(msg.get_charset(), None)
86 eq(msg['content-type'], 'text/plain')
87 # Try adding a charset when there's already MIME headers present
88 msg = Message()
89 msg['MIME-Version'] = '2.0'
90 msg['Content-Type'] = 'text/x-weird'
91 msg['Content-Transfer-Encoding'] = 'quinted-puntable'
92 msg.set_charset(charset)
93 eq(msg['mime-version'], '2.0')
94 eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
95 eq(msg['content-transfer-encoding'], 'quinted-puntable')
96
97 def test_set_charset_from_string(self):
98 eq = self.assertEqual
99 msg = Message()
100 msg.set_charset('us-ascii')
101 eq(msg.get_charset().input_charset, 'us-ascii')
102 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
103
104 def test_set_payload_with_charset(self):
105 msg = Message()
106 charset = Charset('iso-8859-1')
107 msg.set_payload('This is a string payload', charset)
108 self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
109
Barry Warsaw41075852001-09-23 03:18:13 +0000110 def test_get_charsets(self):
111 eq = self.assertEqual
Tim Peters527e64f2001-10-04 05:36:56 +0000112
Barry Warsaw65279d02001-09-26 05:47:08 +0000113 msg = self._msgobj('msg_08.txt')
114 charsets = msg.get_charsets()
115 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000116
Barry Warsaw65279d02001-09-26 05:47:08 +0000117 msg = self._msgobj('msg_09.txt')
118 charsets = msg.get_charsets('dingbat')
119 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
120 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000121
Barry Warsaw65279d02001-09-26 05:47:08 +0000122 msg = self._msgobj('msg_12.txt')
123 charsets = msg.get_charsets()
124 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
125 'iso-8859-3', 'us-ascii', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000126
127 def test_get_filename(self):
128 eq = self.assertEqual
129
Barry Warsaw65279d02001-09-26 05:47:08 +0000130 msg = self._msgobj('msg_04.txt')
131 filenames = [p.get_filename() for p in msg.get_payload()]
Barry Warsaw41075852001-09-23 03:18:13 +0000132 eq(filenames, ['msg.txt', 'msg.txt'])
133
Barry Warsaw65279d02001-09-26 05:47:08 +0000134 msg = self._msgobj('msg_07.txt')
135 subpart = msg.get_payload(1)
Barry Warsaw41075852001-09-23 03:18:13 +0000136 eq(subpart.get_filename(), 'dingusfish.gif')
137
138 def test_get_boundary(self):
139 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000140 msg = self._msgobj('msg_07.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000141 # No quotes!
Barry Warsaw65279d02001-09-26 05:47:08 +0000142 eq(msg.get_boundary(), 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000143
144 def test_set_boundary(self):
145 eq = self.assertEqual
146 # This one has no existing boundary parameter, but the Content-Type:
147 # header appears fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000148 msg = self._msgobj('msg_01.txt')
149 msg.set_boundary('BOUNDARY')
150 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000151 eq(header.lower(), 'content-type')
152 eq(value, 'text/plain; charset=us-ascii; boundary="BOUNDARY"')
153 # This one has a Content-Type: header, with a boundary, stuck in the
154 # middle of its headers. Make sure the order is preserved; it should
155 # be fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000156 msg = self._msgobj('msg_04.txt')
157 msg.set_boundary('BOUNDARY')
158 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000159 eq(header.lower(), 'content-type')
160 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
161 # And this one has no Content-Type: header at all.
Barry Warsaw65279d02001-09-26 05:47:08 +0000162 msg = self._msgobj('msg_03.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000163 self.assertRaises(Errors.HeaderParseError,
Barry Warsaw65279d02001-09-26 05:47:08 +0000164 msg.set_boundary, 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000165
166 def test_get_decoded_payload(self):
167 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000168 msg = self._msgobj('msg_10.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000169 # The outer message is a multipart
Barry Warsaw65279d02001-09-26 05:47:08 +0000170 eq(msg.get_payload(decode=1), None)
Barry Warsaw41075852001-09-23 03:18:13 +0000171 # Subpart 1 is 7bit encoded
Barry Warsaw65279d02001-09-26 05:47:08 +0000172 eq(msg.get_payload(0).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000173 'This is a 7bit encoded message.\n')
174 # Subpart 2 is quopri
Barry Warsaw65279d02001-09-26 05:47:08 +0000175 eq(msg.get_payload(1).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000176 '\xa1This is a Quoted Printable encoded message!\n')
177 # Subpart 3 is base64
Barry Warsaw65279d02001-09-26 05:47:08 +0000178 eq(msg.get_payload(2).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000179 'This is a Base64 encoded message.')
180 # Subpart 4 has no Content-Transfer-Encoding: header.
Barry Warsaw65279d02001-09-26 05:47:08 +0000181 eq(msg.get_payload(3).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000182 'This has no Content-Transfer-Encoding: header.\n')
183
184 def test_decoded_generator(self):
185 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000186 msg = self._msgobj('msg_07.txt')
187 fp = openfile('msg_17.txt')
188 try:
189 text = fp.read()
190 finally:
191 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +0000192 s = StringIO()
193 g = DecodedGenerator(s)
Barry Warsaw2c685062002-06-02 19:09:27 +0000194 g.flatten(msg)
Barry Warsaw65279d02001-09-26 05:47:08 +0000195 eq(s.getvalue(), text)
Barry Warsaw41075852001-09-23 03:18:13 +0000196
197 def test__contains__(self):
198 msg = Message()
199 msg['From'] = 'Me'
200 msg['to'] = 'You'
201 # Check for case insensitivity
202 self.failUnless('from' in msg)
203 self.failUnless('From' in msg)
204 self.failUnless('FROM' in msg)
205 self.failUnless('to' in msg)
206 self.failUnless('To' in msg)
207 self.failUnless('TO' in msg)
208
209 def test_as_string(self):
210 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000211 msg = self._msgobj('msg_01.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000212 fp = openfile('msg_01.txt')
213 try:
214 text = fp.read()
215 finally:
216 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000217 eq(text, msg.as_string())
218 fullrepr = str(msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000219 lines = fullrepr.split('\n')
220 self.failUnless(lines[0].startswith('From '))
221 eq(text, NL.join(lines[1:]))
222
223 def test_bad_param(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000224 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000225 self.assertEqual(msg.get_param('baz'), '')
226
227 def test_missing_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000228 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000229 self.assertEqual(msg.get_filename(), None)
230
231 def test_bogus_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000232 msg = email.message_from_string(
233 "Content-Disposition: blarg; filename\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000234 self.assertEqual(msg.get_filename(), '')
Tim Peters527e64f2001-10-04 05:36:56 +0000235
Barry Warsaw41075852001-09-23 03:18:13 +0000236 def test_missing_boundary(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000237 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000238 self.assertEqual(msg.get_boundary(), None)
239
Barry Warsaw65279d02001-09-26 05:47:08 +0000240 def test_get_params(self):
241 eq = self.assertEqual
242 msg = email.message_from_string(
243 'X-Header: foo=one; bar=two; baz=three\n')
244 eq(msg.get_params(header='x-header'),
245 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
246 msg = email.message_from_string(
247 'X-Header: foo; bar=one; baz=two\n')
248 eq(msg.get_params(header='x-header'),
249 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
250 eq(msg.get_params(), None)
251 msg = email.message_from_string(
252 'X-Header: foo; bar="one"; baz=two\n')
253 eq(msg.get_params(header='x-header'),
254 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
255
Barry Warsaw409a4c02002-04-10 21:01:31 +0000256 def test_get_param_liberal(self):
257 msg = Message()
258 msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
259 self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
260
Barry Warsaw65279d02001-09-26 05:47:08 +0000261 def test_get_param(self):
262 eq = self.assertEqual
263 msg = email.message_from_string(
264 "X-Header: foo=one; bar=two; baz=three\n")
265 eq(msg.get_param('bar', header='x-header'), 'two')
266 eq(msg.get_param('quuz', header='x-header'), None)
267 eq(msg.get_param('quuz'), None)
268 msg = email.message_from_string(
269 'X-Header: foo; bar="one"; baz=two\n')
270 eq(msg.get_param('foo', header='x-header'), '')
271 eq(msg.get_param('bar', header='x-header'), 'one')
272 eq(msg.get_param('baz', header='x-header'), 'two')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000273 # XXX: We are not RFC-2045 compliant! We cannot parse:
274 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
275 # msg.get_param("weird")
276 # yet.
Barry Warsaw65279d02001-09-26 05:47:08 +0000277
Barry Warsaw2539cf52001-10-25 22:43:46 +0000278 def test_get_param_funky_continuation_lines(self):
279 msg = self._msgobj('msg_22.txt')
280 self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
281
Barry Warsaw65279d02001-09-26 05:47:08 +0000282 def test_has_key(self):
283 msg = email.message_from_string('Header: exists')
284 self.failUnless(msg.has_key('header'))
285 self.failUnless(msg.has_key('Header'))
286 self.failUnless(msg.has_key('HEADER'))
287 self.failIf(msg.has_key('headeri'))
288
Barry Warsaw409a4c02002-04-10 21:01:31 +0000289 def test_set_param(self):
290 eq = self.assertEqual
291 msg = Message()
292 msg.set_param('charset', 'iso-2022-jp')
293 eq(msg.get_param('charset'), 'iso-2022-jp')
294 msg.set_param('importance', 'high value')
295 eq(msg.get_param('importance'), 'high value')
296 eq(msg.get_param('importance', unquote=0), '"high value"')
297 eq(msg.get_params(), [('text/plain', ''),
298 ('charset', 'iso-2022-jp'),
299 ('importance', 'high value')])
300 eq(msg.get_params(unquote=0), [('text/plain', ''),
301 ('charset', '"iso-2022-jp"'),
302 ('importance', '"high value"')])
303 msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
304 eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
Barry Warsaw41075852001-09-23 03:18:13 +0000305
Barry Warsaw409a4c02002-04-10 21:01:31 +0000306 def test_del_param(self):
307 eq = self.assertEqual
308 msg = self._msgobj('msg_05.txt')
309 eq(msg.get_params(),
310 [('multipart/report', ''), ('report-type', 'delivery-status'),
311 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
312 old_val = msg.get_param("report-type")
313 msg.del_param("report-type")
314 eq(msg.get_params(),
315 [('multipart/report', ''),
Barry Warsaw16f90552002-04-16 05:06:42 +0000316 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
Barry Warsaw409a4c02002-04-10 21:01:31 +0000317 msg.set_param("report-type", old_val)
318 eq(msg.get_params(),
319 [('multipart/report', ''),
320 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
321 ('report-type', old_val)])
322
323 def test_set_type(self):
324 eq = self.assertEqual
325 msg = Message()
326 self.assertRaises(ValueError, msg.set_type, 'text')
327 msg.set_type('text/plain')
328 eq(msg['content-type'], 'text/plain')
329 msg.set_param('charset', 'us-ascii')
330 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
331 msg.set_type('text/html')
332 eq(msg['content-type'], 'text/html; charset="us-ascii"')
333
Barry Warsaw16f90552002-04-16 05:06:42 +0000334
Barry Warsaw08a534d2001-10-04 17:58:50 +0000335
Barry Warsaw41075852001-09-23 03:18:13 +0000336# Test the email.Encoders module
337class TestEncoders(unittest.TestCase):
338 def test_encode_noop(self):
339 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000340 msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
Barry Warsaw41075852001-09-23 03:18:13 +0000341 eq(msg.get_payload(), 'hello world\n')
Barry Warsaw41075852001-09-23 03:18:13 +0000342
343 def test_encode_7bit(self):
344 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000345 msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000346 eq(msg.get_payload(), 'hello world\n')
347 eq(msg['content-transfer-encoding'], '7bit')
Barry Warsaw65279d02001-09-26 05:47:08 +0000348 msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000349 eq(msg.get_payload(), 'hello \x7f world\n')
350 eq(msg['content-transfer-encoding'], '7bit')
351
352 def test_encode_8bit(self):
353 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000354 msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000355 eq(msg.get_payload(), 'hello \x80 world\n')
356 eq(msg['content-transfer-encoding'], '8bit')
357
Barry Warsaw409a4c02002-04-10 21:01:31 +0000358 def test_encode_empty_payload(self):
359 eq = self.assertEqual
360 msg = Message()
361 msg.set_charset('us-ascii')
362 eq(msg['content-transfer-encoding'], '7bit')
363
Barry Warsaw41075852001-09-23 03:18:13 +0000364 def test_encode_base64(self):
365 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000366 msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
Barry Warsaw41075852001-09-23 03:18:13 +0000367 eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
368 eq(msg['content-transfer-encoding'], 'base64')
369
370 def test_encode_quoted_printable(self):
371 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000372 msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
Barry Warsaw41075852001-09-23 03:18:13 +0000373 eq(msg.get_payload(), 'hello=20world\n')
374 eq(msg['content-transfer-encoding'], 'quoted-printable')
375
Barry Warsaw409a4c02002-04-10 21:01:31 +0000376 def test_default_cte(self):
377 eq = self.assertEqual
378 msg = MIMEText('hello world')
379 eq(msg['content-transfer-encoding'], '7bit')
380
381 def test_default_cte(self):
382 eq = self.assertEqual
383 # With no explicit _charset its us-ascii, and all are 7-bit
384 msg = MIMEText('hello world')
385 eq(msg['content-transfer-encoding'], '7bit')
386 # Similar, but with 8-bit data
387 msg = MIMEText('hello \xf8 world')
388 eq(msg['content-transfer-encoding'], '8bit')
389 # And now with a different charset
390 msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
391 eq(msg['content-transfer-encoding'], 'quoted-printable')
392
Barry Warsaw41075852001-09-23 03:18:13 +0000393
Barry Warsaw08a534d2001-10-04 17:58:50 +0000394
395# Test long header wrapping
Barry Warsaw41075852001-09-23 03:18:13 +0000396class TestLongHeaders(unittest.TestCase):
397 def test_header_splitter(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000398 msg = MIMEText('')
Barry Warsaw41075852001-09-23 03:18:13 +0000399 # It'd be great if we could use add_header() here, but that doesn't
400 # guarantee an order of the parameters.
401 msg['X-Foobar-Spoink-Defrobnit'] = (
402 'wasnipoop; giraffes="very-long-necked-animals"; '
403 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
404 sfp = StringIO()
405 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +0000406 g.flatten(msg)
Barry Warsaw409a4c02002-04-10 21:01:31 +0000407 self.assertEqual(sfp.getvalue(), '''\
408Content-Type: text/plain; charset="us-ascii"
409MIME-Version: 1.0
410Content-Transfer-Encoding: 7bit
411X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
Barry Warsaw16f90552002-04-16 05:06:42 +0000412\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
Barry Warsaw409a4c02002-04-10 21:01:31 +0000413
414''')
Barry Warsaw41075852001-09-23 03:18:13 +0000415
Barry Warsaw07227d12001-10-17 20:52:26 +0000416 def test_no_semis_header_splitter(self):
417 msg = Message()
418 msg['From'] = 'test@dom.ain'
419 refparts = []
420 for i in range(10):
421 refparts.append('<%d@dom.ain>' % i)
422 msg['References'] = SPACE.join(refparts)
423 msg.set_payload('Test')
424 sfp = StringIO()
425 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +0000426 g.flatten(msg)
Barry Warsaw07227d12001-10-17 20:52:26 +0000427 self.assertEqual(sfp.getvalue(), """\
428From: test@dom.ain
429References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
Tim Peterse0c446b2001-10-18 21:57:37 +0000430\t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
Barry Warsaw07227d12001-10-17 20:52:26 +0000431
432Test""")
433
434 def test_no_split_long_header(self):
435 msg = Message()
436 msg['From'] = 'test@dom.ain'
437 refparts = []
438 msg['References'] = 'x' * 80
439 msg.set_payload('Test')
440 sfp = StringIO()
441 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +0000442 g.flatten(msg)
Barry Warsaw07227d12001-10-17 20:52:26 +0000443 self.assertEqual(sfp.getvalue(), """\
444From: test@dom.ain
445References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
446
447Test""")
448
Barry Warsaw409a4c02002-04-10 21:01:31 +0000449 def test_splitting_multiple_long_lines(self):
450 msg = Message()
451 msg['Received'] = """\
452from babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
Barry Warsaw16f90552002-04-16 05:06:42 +0000453\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
454\tfrom babylon.socal-raves.org (localhost [127.0.0.1]); by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81; for <mailman-admin@babylon.socal-raves.org>; Sat, 2 Feb 2002 17:00:06 -0800 (PST)
Barry Warsaw409a4c02002-04-10 21:01:31 +0000455"""
456 self.assertEqual(msg.as_string(), """\
457Received: from babylon.socal-raves.org (localhost [127.0.0.1]);
Barry Warsaw16f90552002-04-16 05:06:42 +0000458\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
459\tfor <mailman-admin@babylon.socal-raves.org>;
460\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
461\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
462\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
463\tfor <mailman-admin@babylon.socal-raves.org>;
464\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
465\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
466\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
467\tfor <mailman-admin@babylon.socal-raves.org>;
468\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
Barry Warsaw409a4c02002-04-10 21:01:31 +0000469
470
471""")
472
Barry Warsaw41075852001-09-23 03:18:13 +0000473
Barry Warsaw08a534d2001-10-04 17:58:50 +0000474
475# Test mangling of "From " lines in the body of a message
Barry Warsaw41075852001-09-23 03:18:13 +0000476class TestFromMangling(unittest.TestCase):
477 def setUp(self):
478 self.msg = Message()
479 self.msg['From'] = 'aaa@bbb.org'
Barry Warsaw2c685062002-06-02 19:09:27 +0000480 self.msg.set_payload("""\
Barry Warsaw41075852001-09-23 03:18:13 +0000481From the desk of A.A.A.:
482Blah blah blah
483""")
484
485 def test_mangled_from(self):
486 s = StringIO()
487 g = Generator(s, mangle_from_=1)
Barry Warsaw2c685062002-06-02 19:09:27 +0000488 g.flatten(self.msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000489 self.assertEqual(s.getvalue(), """\
490From: aaa@bbb.org
491
492>From the desk of A.A.A.:
493Blah blah blah
494""")
495
496 def test_dont_mangle_from(self):
497 s = StringIO()
498 g = Generator(s, mangle_from_=0)
Barry Warsaw2c685062002-06-02 19:09:27 +0000499 g.flatten(self.msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000500 self.assertEqual(s.getvalue(), """\
501From: aaa@bbb.org
502
503From the desk of A.A.A.:
504Blah blah blah
505""")
506
507
Barry Warsaw08a534d2001-10-04 17:58:50 +0000508
Barry Warsawfee435a2001-10-09 19:23:57 +0000509# Test the basic MIMEAudio class
510class TestMIMEAudio(unittest.TestCase):
511 def setUp(self):
512 # In Python, audiotest.au lives in Lib/test not Lib/test/data
513 fp = open(findfile('audiotest.au'))
514 try:
515 self._audiodata = fp.read()
516 finally:
517 fp.close()
518 self._au = MIMEAudio(self._audiodata)
519
520 def test_guess_minor_type(self):
521 self.assertEqual(self._au.get_type(), 'audio/basic')
522
523 def test_encoding(self):
524 payload = self._au.get_payload()
525 self.assertEqual(base64.decodestring(payload), self._audiodata)
526
527 def checkSetMinor(self):
528 au = MIMEAudio(self._audiodata, 'fish')
529 self.assertEqual(im.get_type(), 'audio/fish')
530
531 def test_custom_encoder(self):
532 eq = self.assertEqual
533 def encoder(msg):
534 orig = msg.get_payload()
535 msg.set_payload(0)
536 msg['Content-Transfer-Encoding'] = 'broken64'
537 au = MIMEAudio(self._audiodata, _encoder=encoder)
538 eq(au.get_payload(), 0)
539 eq(au['content-transfer-encoding'], 'broken64')
540
541 def test_add_header(self):
542 eq = self.assertEqual
543 unless = self.failUnless
544 self._au.add_header('Content-Disposition', 'attachment',
545 filename='audiotest.au')
546 eq(self._au['content-disposition'],
547 'attachment; filename="audiotest.au"')
548 eq(self._au.get_params(header='content-disposition'),
549 [('attachment', ''), ('filename', 'audiotest.au')])
550 eq(self._au.get_param('filename', header='content-disposition'),
551 'audiotest.au')
552 missing = []
553 eq(self._au.get_param('attachment', header='content-disposition'), '')
554 unless(self._au.get_param('foo', failobj=missing,
555 header='content-disposition') is missing)
556 # Try some missing stuff
557 unless(self._au.get_param('foobar', missing) is missing)
558 unless(self._au.get_param('attachment', missing,
559 header='foobar') is missing)
560
561
562
Barry Warsaw65279d02001-09-26 05:47:08 +0000563# Test the basic MIMEImage class
564class TestMIMEImage(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000565 def setUp(self):
566 fp = openfile('PyBanner048.gif')
567 try:
568 self._imgdata = fp.read()
569 finally:
570 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000571 self._im = MIMEImage(self._imgdata)
Barry Warsaw41075852001-09-23 03:18:13 +0000572
573 def test_guess_minor_type(self):
574 self.assertEqual(self._im.get_type(), 'image/gif')
575
576 def test_encoding(self):
577 payload = self._im.get_payload()
578 self.assertEqual(base64.decodestring(payload), self._imgdata)
579
580 def checkSetMinor(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000581 im = MIMEImage(self._imgdata, 'fish')
Barry Warsaw41075852001-09-23 03:18:13 +0000582 self.assertEqual(im.get_type(), 'image/fish')
583
584 def test_custom_encoder(self):
585 eq = self.assertEqual
586 def encoder(msg):
587 orig = msg.get_payload()
588 msg.set_payload(0)
589 msg['Content-Transfer-Encoding'] = 'broken64'
Barry Warsaw65279d02001-09-26 05:47:08 +0000590 im = MIMEImage(self._imgdata, _encoder=encoder)
Barry Warsaw41075852001-09-23 03:18:13 +0000591 eq(im.get_payload(), 0)
592 eq(im['content-transfer-encoding'], 'broken64')
593
594 def test_add_header(self):
595 eq = self.assertEqual
596 unless = self.failUnless
597 self._im.add_header('Content-Disposition', 'attachment',
598 filename='dingusfish.gif')
599 eq(self._im['content-disposition'],
600 'attachment; filename="dingusfish.gif"')
601 eq(self._im.get_params(header='content-disposition'),
Barry Warsaw65279d02001-09-26 05:47:08 +0000602 [('attachment', ''), ('filename', 'dingusfish.gif')])
Barry Warsaw41075852001-09-23 03:18:13 +0000603 eq(self._im.get_param('filename', header='content-disposition'),
604 'dingusfish.gif')
605 missing = []
Barry Warsaw65279d02001-09-26 05:47:08 +0000606 eq(self._im.get_param('attachment', header='content-disposition'), '')
607 unless(self._im.get_param('foo', failobj=missing,
Barry Warsaw41075852001-09-23 03:18:13 +0000608 header='content-disposition') is missing)
609 # Try some missing stuff
610 unless(self._im.get_param('foobar', missing) is missing)
611 unless(self._im.get_param('attachment', missing,
612 header='foobar') is missing)
613
614
Barry Warsaw08a534d2001-10-04 17:58:50 +0000615
Barry Warsaw65279d02001-09-26 05:47:08 +0000616# Test the basic MIMEText class
617class TestMIMEText(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000618 def setUp(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000619 self._msg = MIMEText('hello there')
Barry Warsaw41075852001-09-23 03:18:13 +0000620
621 def test_types(self):
622 eq = self.assertEqual
623 unless = self.failUnless
624 eq(self._msg.get_type(), 'text/plain')
625 eq(self._msg.get_param('charset'), 'us-ascii')
626 missing = []
627 unless(self._msg.get_param('foobar', missing) is missing)
628 unless(self._msg.get_param('charset', missing, header='foobar')
629 is missing)
630
631 def test_payload(self):
632 self.assertEqual(self._msg.get_payload(), 'hello there\n')
633 self.failUnless(not self._msg.is_multipart())
634
Barry Warsaw409a4c02002-04-10 21:01:31 +0000635 def test_charset(self):
636 eq = self.assertEqual
637 msg = MIMEText('hello there', _charset='us-ascii')
638 eq(msg.get_charset().input_charset, 'us-ascii')
639 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
640
Barry Warsaw41075852001-09-23 03:18:13 +0000641
Barry Warsaw08a534d2001-10-04 17:58:50 +0000642
643# Test a more complicated multipart/mixed type message
Barry Warsaw41075852001-09-23 03:18:13 +0000644class TestMultipartMixed(unittest.TestCase):
645 def setUp(self):
646 fp = openfile('PyBanner048.gif')
647 try:
648 data = fp.read()
649 finally:
650 fp.close()
651
652 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
Barry Warsaw65279d02001-09-26 05:47:08 +0000653 image = MIMEImage(data, name='dingusfish.gif')
Barry Warsaw41075852001-09-23 03:18:13 +0000654 image.add_header('content-disposition', 'attachment',
655 filename='dingusfish.gif')
Barry Warsaw65279d02001-09-26 05:47:08 +0000656 intro = MIMEText('''\
Barry Warsaw41075852001-09-23 03:18:13 +0000657Hi there,
658
659This is the dingus fish.
660''')
Barry Warsaw2c685062002-06-02 19:09:27 +0000661 container.attach(intro)
662 container.attach(image)
Barry Warsaw41075852001-09-23 03:18:13 +0000663 container['From'] = 'Barry <barry@digicool.com>'
664 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
665 container['Subject'] = 'Here is your dingus fish'
Tim Peters527e64f2001-10-04 05:36:56 +0000666
Barry Warsaw41075852001-09-23 03:18:13 +0000667 now = 987809702.54848599
668 timetuple = time.localtime(now)
669 if timetuple[-1] == 0:
670 tzsecs = time.timezone
671 else:
672 tzsecs = time.altzone
673 if tzsecs > 0:
674 sign = '-'
675 else:
676 sign = '+'
677 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
678 container['Date'] = time.strftime(
679 '%a, %d %b %Y %H:%M:%S',
680 time.localtime(now)) + tzoffset
681 self._msg = container
682 self._im = image
683 self._txt = intro
684
685 def test_hierarchy(self):
686 # convenience
687 eq = self.assertEqual
688 unless = self.failUnless
689 raises = self.assertRaises
690 # tests
691 m = self._msg
692 unless(m.is_multipart())
693 eq(m.get_type(), 'multipart/mixed')
694 eq(len(m.get_payload()), 2)
695 raises(IndexError, m.get_payload, 2)
696 m0 = m.get_payload(0)
697 m1 = m.get_payload(1)
698 unless(m0 is self._txt)
699 unless(m1 is self._im)
700 eq(m.get_payload(), [m0, m1])
701 unless(not m0.is_multipart())
702 unless(not m1.is_multipart())
703
Barry Warsaw409a4c02002-04-10 21:01:31 +0000704 def test_no_parts_in_a_multipart(self):
705 outer = MIMEBase('multipart', 'mixed')
706 outer['Subject'] = 'A subject'
707 outer['To'] = 'aperson@dom.ain'
708 outer['From'] = 'bperson@dom.ain'
709 outer.preamble = ''
710 outer.epilogue = ''
711 outer.set_boundary('BOUNDARY')
712 msg = MIMEText('hello world')
713 self.assertEqual(outer.as_string(), '''\
714Content-Type: multipart/mixed; boundary="BOUNDARY"
715MIME-Version: 1.0
716Subject: A subject
717To: aperson@dom.ain
718From: bperson@dom.ain
719
720--BOUNDARY
721
722
723--BOUNDARY--
Barry Warsaw16f90552002-04-16 05:06:42 +0000724''')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000725
726 def test_one_part_in_a_multipart(self):
727 outer = MIMEBase('multipart', 'mixed')
728 outer['Subject'] = 'A subject'
729 outer['To'] = 'aperson@dom.ain'
730 outer['From'] = 'bperson@dom.ain'
731 outer.preamble = ''
732 outer.epilogue = ''
733 outer.set_boundary('BOUNDARY')
734 msg = MIMEText('hello world')
735 outer.attach(msg)
736 self.assertEqual(outer.as_string(), '''\
737Content-Type: multipart/mixed; boundary="BOUNDARY"
738MIME-Version: 1.0
739Subject: A subject
740To: aperson@dom.ain
741From: bperson@dom.ain
742
743--BOUNDARY
744Content-Type: text/plain; charset="us-ascii"
745MIME-Version: 1.0
746Content-Transfer-Encoding: 7bit
747
748hello world
749
750--BOUNDARY--
Barry Warsaw16f90552002-04-16 05:06:42 +0000751''')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000752
753 def test_seq_parts_in_a_multipart(self):
754 outer = MIMEBase('multipart', 'mixed')
755 outer['Subject'] = 'A subject'
756 outer['To'] = 'aperson@dom.ain'
757 outer['From'] = 'bperson@dom.ain'
758 outer.preamble = ''
759 outer.epilogue = ''
760 msg = MIMEText('hello world')
761 outer.attach(msg)
762 outer.set_boundary('BOUNDARY')
763 self.assertEqual(outer.as_string(), '''\
764Content-Type: multipart/mixed; boundary="BOUNDARY"
765MIME-Version: 1.0
766Subject: A subject
767To: aperson@dom.ain
768From: bperson@dom.ain
769
770--BOUNDARY
771Content-Type: text/plain; charset="us-ascii"
772MIME-Version: 1.0
773Content-Transfer-Encoding: 7bit
774
775hello world
776
777--BOUNDARY--
Barry Warsaw16f90552002-04-16 05:06:42 +0000778''')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000779
Barry Warsaw41075852001-09-23 03:18:13 +0000780
Barry Warsaw08a534d2001-10-04 17:58:50 +0000781
782# Test some badly formatted messages
Barry Warsaw41075852001-09-23 03:18:13 +0000783class TestNonConformant(TestEmailBase):
784 def test_parse_missing_minor_type(self):
785 eq = self.assertEqual
786 msg = self._msgobj('msg_14.txt')
787 eq(msg.get_type(), 'text')
788 eq(msg.get_main_type(), 'text')
789 self.failUnless(msg.get_subtype() is None)
790
791 def test_bogus_boundary(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +0000792 fp = openfile(findfile('msg_15.txt'))
Barry Warsaw41075852001-09-23 03:18:13 +0000793 try:
794 data = fp.read()
795 finally:
796 fp.close()
797 p = Parser()
798 # Note, under a future non-strict parsing mode, this would parse the
799 # message into the intended message tree.
800 self.assertRaises(Errors.BoundaryError, p.parsestr, data)
801
Barry Warsaw409a4c02002-04-10 21:01:31 +0000802 def test_multipart_no_boundary(self):
803 fp = openfile(findfile('msg_25.txt'))
804 self.assertRaises(Errors.BoundaryError, email.message_from_file, fp)
805
Barry Warsaw41075852001-09-23 03:18:13 +0000806
Barry Warsaw08a534d2001-10-04 17:58:50 +0000807
808# Test RFC 2047 header encoding and decoding
Barry Warsaw41075852001-09-23 03:18:13 +0000809class TestRFC2047(unittest.TestCase):
810 def test_iso_8859_1(self):
811 eq = self.assertEqual
812 s = '=?iso-8859-1?q?this=20is=20some=20text?='
813 eq(Utils.decode(s), 'this is some text')
814 s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
Barry Warsaw409a4c02002-04-10 21:01:31 +0000815 eq(Utils.decode(s), u'Keld J\xf8rn Simonsen')
Barry Warsaw41075852001-09-23 03:18:13 +0000816 s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
817 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
818 eq(Utils.decode(s), 'If you can read this you understand the example.')
819 s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
820 eq(Utils.decode(s),
821 u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
822 s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
Barry Warsaw409a4c02002-04-10 21:01:31 +0000823 eq(Utils.decode(s), u'this issome text')
824 s = '=?iso-8859-1?q?this=20is_?= =?iso-8859-1?q?some=20text?='
Barry Warsaw41075852001-09-23 03:18:13 +0000825 eq(Utils.decode(s), u'this is some text')
826
827 def test_encode_header(self):
828 eq = self.assertEqual
829 s = 'this is some text'
830 eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
831 s = 'Keld_J\xf8rn_Simonsen'
832 eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
833 s1 = 'If you can read this yo'
834 s2 = 'u understand the example.'
835 eq(Utils.encode(s1, encoding='b'),
836 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
837 eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
838 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
839
840
Barry Warsaw08a534d2001-10-04 17:58:50 +0000841
842# Test the MIMEMessage class
Barry Warsaw65279d02001-09-26 05:47:08 +0000843class TestMIMEMessage(TestEmailBase):
Barry Warsaw41075852001-09-23 03:18:13 +0000844 def setUp(self):
845 fp = openfile('msg_11.txt')
846 self._text = fp.read()
847 fp.close()
848
849 def test_type_error(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000850 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
Barry Warsaw41075852001-09-23 03:18:13 +0000851
852 def test_valid_argument(self):
853 eq = self.assertEqual
Barry Warsaw2c685062002-06-02 19:09:27 +0000854 unless = self.failUnless
Barry Warsaw41075852001-09-23 03:18:13 +0000855 subject = 'A sub-message'
856 m = Message()
857 m['Subject'] = subject
Barry Warsaw65279d02001-09-26 05:47:08 +0000858 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000859 eq(r.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +0000860 payload = r.get_payload()
861 unless(type(payload), ListType)
862 eq(len(payload), 1)
863 subpart = payload[0]
864 unless(subpart is m)
865 eq(subpart['subject'], subject)
866
867 def test_bad_multipart(self):
868 eq = self.assertEqual
869 msg1 = Message()
870 msg1['Subject'] = 'subpart 1'
871 msg2 = Message()
872 msg2['Subject'] = 'subpart 2'
873 r = MIMEMessage(msg1)
874 self.assertRaises(Errors.MultipartConversionError, r.attach, msg2)
Barry Warsaw41075852001-09-23 03:18:13 +0000875
876 def test_generate(self):
877 # First craft the message to be encapsulated
878 m = Message()
879 m['Subject'] = 'An enclosed message'
Barry Warsaw2c685062002-06-02 19:09:27 +0000880 m.set_payload('Here is the body of the message.\n')
Barry Warsaw65279d02001-09-26 05:47:08 +0000881 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000882 r['Subject'] = 'The enclosing message'
883 s = StringIO()
884 g = Generator(s)
Barry Warsaw2c685062002-06-02 19:09:27 +0000885 g.flatten(r)
Barry Warsaw65279d02001-09-26 05:47:08 +0000886 self.assertEqual(s.getvalue(), """\
887Content-Type: message/rfc822
888MIME-Version: 1.0
889Subject: The enclosing message
890
891Subject: An enclosed message
892
893Here is the body of the message.
894""")
895
896 def test_parse_message_rfc822(self):
897 eq = self.assertEqual
Barry Warsaw2c685062002-06-02 19:09:27 +0000898 unless = self.failUnless
Barry Warsaw65279d02001-09-26 05:47:08 +0000899 msg = self._msgobj('msg_11.txt')
900 eq(msg.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +0000901 payload = msg.get_payload()
902 unless(isinstance(payload, ListType))
903 eq(len(payload), 1)
904 submsg = payload[0]
Barry Warsaw65279d02001-09-26 05:47:08 +0000905 self.failUnless(isinstance(submsg, Message))
906 eq(submsg['subject'], 'An enclosed message')
907 eq(submsg.get_payload(), 'Here is the body of the message.\n')
908
909 def test_dsn(self):
910 eq = self.assertEqual
911 unless = self.failUnless
912 # msg 16 is a Delivery Status Notification, see RFC XXXX
913 msg = self._msgobj('msg_16.txt')
914 eq(msg.get_type(), 'multipart/report')
915 unless(msg.is_multipart())
916 eq(len(msg.get_payload()), 3)
917 # Subpart 1 is a text/plain, human readable section
918 subpart = msg.get_payload(0)
919 eq(subpart.get_type(), 'text/plain')
920 eq(subpart.get_payload(), """\
921This report relates to a message you sent with the following header fields:
922
923 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
924 Date: Sun, 23 Sep 2001 20:10:55 -0700
925 From: "Ian T. Henry" <henryi@oxy.edu>
926 To: SoCal Raves <scr@socal-raves.org>
927 Subject: [scr] yeah for Ians!!
928
929Your message cannot be delivered to the following recipients:
930
931 Recipient address: jangel1@cougar.noc.ucla.edu
932 Reason: recipient reached disk quota
933
934""")
935 # Subpart 2 contains the machine parsable DSN information. It
936 # consists of two blocks of headers, represented by two nested Message
937 # objects.
938 subpart = msg.get_payload(1)
939 eq(subpart.get_type(), 'message/delivery-status')
940 eq(len(subpart.get_payload()), 2)
941 # message/delivery-status should treat each block as a bunch of
942 # headers, i.e. a bunch of Message objects.
943 dsn1 = subpart.get_payload(0)
944 unless(isinstance(dsn1, Message))
945 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
946 eq(dsn1.get_param('dns', header='reporting-mta'), '')
947 # Try a missing one <wink>
948 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
949 dsn2 = subpart.get_payload(1)
950 unless(isinstance(dsn2, Message))
951 eq(dsn2['action'], 'failed')
952 eq(dsn2.get_params(header='original-recipient'),
953 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
954 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
955 # Subpart 3 is the original message
956 subpart = msg.get_payload(2)
957 eq(subpart.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +0000958 payload = subpart.get_payload()
959 unless(isinstance(payload, ListType))
960 eq(len(payload), 1)
961 subsubpart = payload[0]
Barry Warsaw65279d02001-09-26 05:47:08 +0000962 unless(isinstance(subsubpart, Message))
963 eq(subsubpart.get_type(), 'text/plain')
964 eq(subsubpart['message-id'],
965 '<002001c144a6$8752e060$56104586@oxy.edu>')
Barry Warsaw41075852001-09-23 03:18:13 +0000966
Barry Warsaw1f0fa922001-10-19 04:08:59 +0000967 def test_epilogue(self):
968 fp = openfile('msg_21.txt')
969 try:
970 text = fp.read()
971 finally:
972 fp.close()
973 msg = Message()
974 msg['From'] = 'aperson@dom.ain'
975 msg['To'] = 'bperson@dom.ain'
976 msg['Subject'] = 'Test'
977 msg.preamble = 'MIME message\n'
978 msg.epilogue = 'End of MIME message\n'
979 msg1 = MIMEText('One')
980 msg2 = MIMEText('Two')
981 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
Barry Warsaw2c685062002-06-02 19:09:27 +0000982 msg.attach(msg1)
983 msg.attach(msg2)
Barry Warsaw1f0fa922001-10-19 04:08:59 +0000984 sfp = StringIO()
985 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +0000986 g.flatten(msg)
Barry Warsaw1f0fa922001-10-19 04:08:59 +0000987 self.assertEqual(sfp.getvalue(), text)
988
Barry Warsaw41075852001-09-23 03:18:13 +0000989
Barry Warsaw08a534d2001-10-04 17:58:50 +0000990
991# A general test of parser->model->generator idempotency. IOW, read a message
992# in, parse it into a message object tree, then without touching the tree,
993# regenerate the plain text. The original text and the transformed text
994# should be identical. Note: that we ignore the Unix-From since that may
995# contain a changed date.
Barry Warsaw41075852001-09-23 03:18:13 +0000996class TestIdempotent(unittest.TestCase):
997 def _msgobj(self, filename):
998 fp = openfile(filename)
999 try:
1000 data = fp.read()
1001 finally:
1002 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +00001003 msg = email.message_from_string(data)
1004 return msg, data
Barry Warsaw41075852001-09-23 03:18:13 +00001005
1006 def _idempotent(self, msg, text):
1007 eq = self.assertEquals
1008 s = StringIO()
1009 g = Generator(s, maxheaderlen=0)
Barry Warsaw2c685062002-06-02 19:09:27 +00001010 g.flatten(msg)
Barry Warsaw41075852001-09-23 03:18:13 +00001011 eq(text, s.getvalue())
1012
1013 def test_parse_text_message(self):
1014 eq = self.assertEquals
1015 msg, text = self._msgobj('msg_01.txt')
1016 eq(msg.get_type(), 'text/plain')
1017 eq(msg.get_main_type(), 'text')
1018 eq(msg.get_subtype(), 'plain')
Barry Warsaw65279d02001-09-26 05:47:08 +00001019 eq(msg.get_params()[1], ('charset', 'us-ascii'))
Barry Warsaw41075852001-09-23 03:18:13 +00001020 eq(msg.get_param('charset'), 'us-ascii')
1021 eq(msg.preamble, None)
1022 eq(msg.epilogue, None)
1023 self._idempotent(msg, text)
1024
1025 def test_parse_untyped_message(self):
1026 eq = self.assertEquals
1027 msg, text = self._msgobj('msg_03.txt')
1028 eq(msg.get_type(), None)
1029 eq(msg.get_params(), None)
1030 eq(msg.get_param('charset'), None)
1031 self._idempotent(msg, text)
1032
1033 def test_simple_multipart(self):
1034 msg, text = self._msgobj('msg_04.txt')
1035 self._idempotent(msg, text)
1036
1037 def test_MIME_digest(self):
1038 msg, text = self._msgobj('msg_02.txt')
1039 self._idempotent(msg, text)
1040
1041 def test_mixed_with_image(self):
1042 msg, text = self._msgobj('msg_06.txt')
1043 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +00001044
Barry Warsaw41075852001-09-23 03:18:13 +00001045 def test_multipart_report(self):
1046 msg, text = self._msgobj('msg_05.txt')
1047 self._idempotent(msg, text)
Barry Warsaw65279d02001-09-26 05:47:08 +00001048
1049 def test_dsn(self):
1050 msg, text = self._msgobj('msg_16.txt')
1051 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +00001052
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001053 def test_preamble_epilogue(self):
1054 msg, text = self._msgobj('msg_21.txt')
1055 self._idempotent(msg, text)
1056
Barry Warsaw763af412002-01-27 06:48:47 +00001057 def test_multipart_one_part(self):
1058 msg, text = self._msgobj('msg_23.txt')
1059 self._idempotent(msg, text)
1060
Barry Warsaw409a4c02002-04-10 21:01:31 +00001061 def test_multipart_no_parts(self):
1062 msg, text = self._msgobj('msg_24.txt')
1063 self._idempotent(msg, text)
1064
Barry Warsaw41075852001-09-23 03:18:13 +00001065 def test_content_type(self):
1066 eq = self.assertEquals
Barry Warsaw2c685062002-06-02 19:09:27 +00001067 unless = self.failUnless
Barry Warsaw41075852001-09-23 03:18:13 +00001068 # Get a message object and reset the seek pointer for other tests
1069 msg, text = self._msgobj('msg_05.txt')
1070 eq(msg.get_type(), 'multipart/report')
1071 # Test the Content-Type: parameters
1072 params = {}
Barry Warsaw65279d02001-09-26 05:47:08 +00001073 for pk, pv in msg.get_params():
Barry Warsaw41075852001-09-23 03:18:13 +00001074 params[pk] = pv
1075 eq(params['report-type'], 'delivery-status')
Barry Warsaw65279d02001-09-26 05:47:08 +00001076 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
Barry Warsaw41075852001-09-23 03:18:13 +00001077 eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
1078 eq(msg.epilogue, '\n\n')
1079 eq(len(msg.get_payload()), 3)
1080 # Make sure the subparts are what we expect
1081 msg1 = msg.get_payload(0)
1082 eq(msg1.get_type(), 'text/plain')
1083 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
1084 msg2 = msg.get_payload(1)
1085 eq(msg2.get_type(), None)
1086 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
1087 msg3 = msg.get_payload(2)
1088 eq(msg3.get_type(), 'message/rfc822')
1089 self.failUnless(isinstance(msg3, Message))
Barry Warsaw2c685062002-06-02 19:09:27 +00001090 payload = msg3.get_payload()
1091 unless(isinstance(payload, ListType))
1092 eq(len(payload), 1)
1093 msg4 = payload[0]
1094 unless(isinstance(msg4, Message))
Barry Warsaw41075852001-09-23 03:18:13 +00001095 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
1096
1097 def test_parser(self):
1098 eq = self.assertEquals
Barry Warsaw2c685062002-06-02 19:09:27 +00001099 unless = self.failUnless
Barry Warsaw41075852001-09-23 03:18:13 +00001100 msg, text = self._msgobj('msg_06.txt')
1101 # Check some of the outer headers
1102 eq(msg.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +00001103 # Make sure the payload is a list of exactly one sub-Message, and that
1104 # that submessage has a type of text/plain
1105 payload = msg.get_payload()
1106 unless(isinstance(payload, ListType))
1107 eq(len(payload), 1)
1108 msg1 = payload[0]
Barry Warsaw41075852001-09-23 03:18:13 +00001109 self.failUnless(isinstance(msg1, Message))
1110 eq(msg1.get_type(), 'text/plain')
1111 self.failUnless(isinstance(msg1.get_payload(), StringType))
1112 eq(msg1.get_payload(), '\n')
Barry Warsaw41075852001-09-23 03:18:13 +00001113
Barry Warsaw08a534d2001-10-04 17:58:50 +00001114
1115# Test various other bits of the package's functionality
Barry Warsaw41075852001-09-23 03:18:13 +00001116class TestMiscellaneous(unittest.TestCase):
1117 def test_message_from_string(self):
1118 fp = openfile('msg_01.txt')
1119 try:
1120 text = fp.read()
1121 finally:
1122 fp.close()
1123 msg = email.message_from_string(text)
1124 s = StringIO()
1125 # Don't wrap/continue long headers since we're trying to test
1126 # idempotency.
1127 g = Generator(s, maxheaderlen=0)
Barry Warsaw2c685062002-06-02 19:09:27 +00001128 g.flatten(msg)
Barry Warsaw41075852001-09-23 03:18:13 +00001129 self.assertEqual(text, s.getvalue())
1130
1131 def test_message_from_file(self):
1132 fp = openfile('msg_01.txt')
1133 try:
1134 text = fp.read()
1135 fp.seek(0)
1136 msg = email.message_from_file(fp)
1137 s = StringIO()
1138 # Don't wrap/continue long headers since we're trying to test
1139 # idempotency.
1140 g = Generator(s, maxheaderlen=0)
Barry Warsaw2c685062002-06-02 19:09:27 +00001141 g.flatten(msg)
Barry Warsaw41075852001-09-23 03:18:13 +00001142 self.assertEqual(text, s.getvalue())
1143 finally:
1144 fp.close()
1145
1146 def test_message_from_string_with_class(self):
1147 unless = self.failUnless
1148 fp = openfile('msg_01.txt')
1149 try:
1150 text = fp.read()
1151 finally:
1152 fp.close()
1153 # Create a subclass
1154 class MyMessage(Message):
1155 pass
Tim Peters527e64f2001-10-04 05:36:56 +00001156
Barry Warsaw41075852001-09-23 03:18:13 +00001157 msg = email.message_from_string(text, MyMessage)
1158 unless(isinstance(msg, MyMessage))
1159 # Try something more complicated
1160 fp = openfile('msg_02.txt')
1161 try:
1162 text = fp.read()
1163 finally:
1164 fp.close()
1165 msg = email.message_from_string(text, MyMessage)
1166 for subpart in msg.walk():
1167 unless(isinstance(subpart, MyMessage))
1168
Barry Warsaw41075852001-09-23 03:18:13 +00001169 def test_message_from_file_with_class(self):
1170 unless = self.failUnless
1171 # Create a subclass
1172 class MyMessage(Message):
1173 pass
Tim Peters527e64f2001-10-04 05:36:56 +00001174
Barry Warsaw41075852001-09-23 03:18:13 +00001175 fp = openfile('msg_01.txt')
1176 try:
1177 msg = email.message_from_file(fp, MyMessage)
1178 finally:
1179 fp.close()
1180 unless(isinstance(msg, MyMessage))
1181 # Try something more complicated
1182 fp = openfile('msg_02.txt')
1183 try:
1184 msg = email.message_from_file(fp, MyMessage)
1185 finally:
1186 fp.close()
1187 for subpart in msg.walk():
1188 unless(isinstance(subpart, MyMessage))
1189
Barry Warsawfee435a2001-10-09 19:23:57 +00001190 def test__all__(self):
1191 module = __import__('email')
1192 all = module.__all__
1193 all.sort()
Barry Warsaw16f90552002-04-16 05:06:42 +00001194 self.assertEqual(all, ['Charset', 'Encoders', 'Errors', 'Generator',
1195 'Header', 'Iterators', 'MIMEAudio',
1196 'MIMEBase', 'MIMEImage', 'MIMEMessage',
Barry Warsaw409a4c02002-04-10 21:01:31 +00001197 'MIMEText', 'Message', 'Parser',
Barry Warsaw16f90552002-04-16 05:06:42 +00001198 'Utils', 'base64MIME',
Barry Warsaw409a4c02002-04-10 21:01:31 +00001199 'message_from_file', 'message_from_string',
1200 'quopriMIME'])
Barry Warsawfee435a2001-10-09 19:23:57 +00001201
Barry Warsaw75edc6a2001-11-09 17:46:17 +00001202 def test_formatdate(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001203 now = time.time()
1204 self.assertEqual(Utils.parsedate(Utils.formatdate(now))[:6],
1205 time.gmtime(now)[:6])
Barry Warsaw75edc6a2001-11-09 17:46:17 +00001206
Barry Warsaw4586d2c2001-11-19 18:38:42 +00001207 def test_formatdate_localtime(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001208 now = time.time()
1209 self.assertEqual(
1210 Utils.parsedate(Utils.formatdate(now, localtime=1))[:6],
1211 time.localtime(now)[:6])
Barry Warsaw75a40fc2001-11-19 16:31:06 +00001212
1213 def test_parsedate_none(self):
Barry Warsaw19c10ca2001-11-13 18:01:37 +00001214 self.assertEqual(Utils.parsedate(''), None)
1215
Barry Warsaweae36ac2001-12-20 16:37:27 +00001216 def test_parseaddr_empty(self):
1217 self.assertEqual(Utils.parseaddr('<>'), ('', ''))
Barry Warsaw409a4c02002-04-10 21:01:31 +00001218 self.assertEqual(Utils.formataddr(Utils.parseaddr('<>')), '')
1219
1220 def test_noquote_dump(self):
1221 self.assertEqual(
1222 Utils.formataddr(('A Silly Person', 'person@dom.ain')),
1223 'A Silly Person <person@dom.ain>')
1224
1225 def test_escape_dump(self):
1226 self.assertEqual(
1227 Utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
1228 r'"A \(Very\) Silly Person" <person@dom.ain>')
1229 a = r'A \(Special\) Person'
1230 b = 'person@dom.ain'
1231 self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
1232
1233 def test_quote_dump(self):
1234 self.assertEqual(
1235 Utils.formataddr(('A Silly; Person', 'person@dom.ain')),
1236 r'"A Silly; Person" <person@dom.ain>')
1237
1238 def test_fix_eols(self):
1239 eq = self.assertEqual
1240 eq(Utils.fix_eols('hello'), 'hello')
1241 eq(Utils.fix_eols('hello\n'), 'hello\r\n')
1242 eq(Utils.fix_eols('hello\r'), 'hello\r\n')
1243 eq(Utils.fix_eols('hello\r\n'), 'hello\r\n')
1244 eq(Utils.fix_eols('hello\n\r'), 'hello\r\n\r\n')
1245
1246 def test_charset_richcomparisons(self):
1247 eq = self.assertEqual
1248 ne = self.failIfEqual
1249 cset1 = Charset()
1250 cset2 = Charset()
1251 eq(cset1, 'us-ascii')
1252 eq(cset1, 'US-ASCII')
1253 eq(cset1, 'Us-AsCiI')
1254 eq('us-ascii', cset1)
1255 eq('US-ASCII', cset1)
1256 eq('Us-AsCiI', cset1)
1257 ne(cset1, 'usascii')
1258 ne(cset1, 'USASCII')
1259 ne(cset1, 'UsAsCiI')
1260 ne('usascii', cset1)
1261 ne('USASCII', cset1)
1262 ne('UsAsCiI', cset1)
1263 eq(cset1, cset2)
1264 eq(cset2, cset1)
Barry Warsaweae36ac2001-12-20 16:37:27 +00001265
Barry Warsaw4be9ecc2002-05-22 01:52:10 +00001266 def test_getaddresses(self):
1267 eq = self.assertEqual
1268 eq(Utils.getaddresses(['aperson@dom.ain (Al Person)',
1269 'Bud Person <bperson@dom.ain>']),
1270 [('Al Person', 'aperson@dom.ain'),
1271 ('Bud Person', 'bperson@dom.ain')])
1272
Barry Warsaw41075852001-09-23 03:18:13 +00001273
Barry Warsaw08a534d2001-10-04 17:58:50 +00001274
1275# Test the iterator/generators
Barry Warsaw41075852001-09-23 03:18:13 +00001276class TestIterators(TestEmailBase):
1277 def test_body_line_iterator(self):
1278 eq = self.assertEqual
1279 # First a simple non-multipart message
1280 msg = self._msgobj('msg_01.txt')
1281 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +00001282 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +00001283 eq(len(lines), 6)
1284 eq(EMPTYSTRING.join(lines), msg.get_payload())
1285 # Now a more complicated multipart
1286 msg = self._msgobj('msg_02.txt')
1287 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +00001288 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +00001289 eq(len(lines), 43)
Barry Warsaw08a534d2001-10-04 17:58:50 +00001290 eq(EMPTYSTRING.join(lines), openfile('msg_19.txt').read())
Barry Warsaw41075852001-09-23 03:18:13 +00001291
1292 def test_typed_subpart_iterator(self):
1293 eq = self.assertEqual
1294 msg = self._msgobj('msg_04.txt')
1295 it = Iterators.typed_subpart_iterator(msg, 'text')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001296 lines = []
1297 subparts = 0
1298 for subpart in it:
1299 subparts += 1
1300 lines.append(subpart.get_payload())
1301 eq(subparts, 2)
Barry Warsaw41075852001-09-23 03:18:13 +00001302 eq(EMPTYSTRING.join(lines), """\
1303a simple kind of mirror
1304to reflect upon our own
1305a simple kind of mirror
1306to reflect upon our own
1307""")
1308
Barry Warsawcdc632c2001-10-15 04:39:02 +00001309 def test_typed_subpart_iterator_default_type(self):
1310 eq = self.assertEqual
1311 msg = self._msgobj('msg_03.txt')
1312 it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
1313 lines = []
1314 subparts = 0
1315 for subpart in it:
1316 subparts += 1
1317 lines.append(subpart.get_payload())
1318 eq(subparts, 1)
1319 eq(EMPTYSTRING.join(lines), """\
1320
1321Hi,
1322
1323Do you like this message?
1324
1325-Me
1326""")
Barry Warsaw41075852001-09-23 03:18:13 +00001327
Barry Warsaw409a4c02002-04-10 21:01:31 +00001328
Barry Warsaw08a534d2001-10-04 17:58:50 +00001329
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001330class TestParsers(unittest.TestCase):
1331 def test_header_parser(self):
1332 eq = self.assertEqual
1333 # Parse only the headers of a complex multipart MIME document
1334 p = HeaderParser()
1335 fp = openfile('msg_02.txt')
1336 msg = p.parse(fp)
1337 eq(msg['from'], 'ppp-request@zzz.org')
1338 eq(msg['to'], 'ppp@zzz.org')
1339 eq(msg.get_type(), 'multipart/mixed')
1340 eq(msg.is_multipart(), 0)
1341 self.failUnless(isinstance(msg.get_payload(), StringType))
1342
Barry Warsaw409a4c02002-04-10 21:01:31 +00001343 def test_whitespace_continuaton(self):
1344 eq = self.assertEqual
1345 # This message contains a line after the Subject: header that has only
1346 # whitespace, but it is not empty!
1347 msg = email.message_from_string("""\
1348From: aperson@dom.ain
1349To: bperson@dom.ain
1350Subject: the next line has a space on it
Barry Warsaw16f90552002-04-16 05:06:42 +00001351\x20
Barry Warsaw409a4c02002-04-10 21:01:31 +00001352Date: Mon, 8 Apr 2002 15:09:19 -0400
1353Message-ID: spam
1354
1355Here's the message body
1356""")
1357 eq(msg['subject'], 'the next line has a space on it\n ')
1358 eq(msg['message-id'], 'spam')
1359 eq(msg.get_payload(), "Here's the message body\n")
1360
Barry Warsawe0d85c82002-05-19 23:52:54 +00001361 def test_crlf_separation(self):
1362 eq = self.assertEqual
1363 fp = openfile('msg_26.txt')
1364 p = Parser()
1365 msg = p.parse(fp)
1366 eq(len(msg.get_payload()), 2)
1367 part1 = msg.get_payload(0)
1368 eq(part1.get_type(), 'text/plain')
1369 eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
1370 part2 = msg.get_payload(1)
1371 eq(part2.get_type(), 'application/riscos')
1372
Barry Warsaw409a4c02002-04-10 21:01:31 +00001373
1374
1375class TestBase64(unittest.TestCase):
1376 def test_len(self):
1377 eq = self.assertEqual
1378 eq(base64MIME.base64_len('hello'),
1379 len(base64MIME.encode('hello', eol='')))
1380 for size in range(15):
1381 if size == 0 : bsize = 0
1382 elif size <= 3 : bsize = 4
1383 elif size <= 6 : bsize = 8
1384 elif size <= 9 : bsize = 12
1385 elif size <= 12: bsize = 16
1386 else : bsize = 20
1387 eq(base64MIME.base64_len('x'*size), bsize)
1388
1389 def test_decode(self):
1390 eq = self.assertEqual
1391 eq(base64MIME.decode(''), '')
1392 eq(base64MIME.decode('aGVsbG8='), 'hello')
1393 eq(base64MIME.decode('aGVsbG8=', 'X'), 'hello')
1394 eq(base64MIME.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
1395
1396 def test_encode(self):
1397 eq = self.assertEqual
1398 eq(base64MIME.encode(''), '')
1399 eq(base64MIME.encode('hello'), 'aGVsbG8=\n')
1400 # Test the binary flag
1401 eq(base64MIME.encode('hello\n'), 'aGVsbG8K\n')
1402 eq(base64MIME.encode('hello\n', 0), 'aGVsbG8NCg==\n')
1403 # Test the maxlinelen arg
1404 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40), """\
1405eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1406eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1407eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1408eHh4eCB4eHh4IA==
1409""")
1410 # Test the eol argument
1411 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1412eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1413eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1414eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1415eHh4eCB4eHh4IA==\r
1416""")
Barry Warsaw16f90552002-04-16 05:06:42 +00001417
Barry Warsaw409a4c02002-04-10 21:01:31 +00001418 def test_header_encode(self):
1419 eq = self.assertEqual
1420 he = base64MIME.header_encode
1421 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
1422 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
1423 # Test the charset option
1424 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
1425 # Test the keep_eols flag
1426 eq(he('hello\nworld', keep_eols=1),
1427 '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
1428 # Test the maxlinelen argument
1429 eq(he('xxxx ' * 20, maxlinelen=40), """\
1430=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
1431 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
1432 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
1433 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
1434 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
1435 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1436 # Test the eol argument
1437 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1438=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
1439 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
1440 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
1441 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
1442 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
1443 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1444
1445
1446
1447class TestQuopri(unittest.TestCase):
1448 def setUp(self):
1449 self.hlit = [chr(x) for x in range(ord('a'), ord('z')+1)] + \
1450 [chr(x) for x in range(ord('A'), ord('Z')+1)] + \
1451 [chr(x) for x in range(ord('0'), ord('9')+1)] + \
1452 ['!', '*', '+', '-', '/', ' ']
1453 self.hnon = [chr(x) for x in range(256) if chr(x) not in self.hlit]
1454 assert len(self.hlit) + len(self.hnon) == 256
1455 self.blit = [chr(x) for x in range(ord(' '), ord('~')+1)] + ['\t']
1456 self.blit.remove('=')
1457 self.bnon = [chr(x) for x in range(256) if chr(x) not in self.blit]
1458 assert len(self.blit) + len(self.bnon) == 256
1459
1460 def test_header_quopri_check(self):
1461 for c in self.hlit:
1462 self.failIf(quopriMIME.header_quopri_check(c))
1463 for c in self.hnon:
1464 self.failUnless(quopriMIME.header_quopri_check(c))
1465
1466 def test_body_quopri_check(self):
1467 for c in self.blit:
1468 self.failIf(quopriMIME.body_quopri_check(c))
1469 for c in self.bnon:
1470 self.failUnless(quopriMIME.body_quopri_check(c))
1471
1472 def test_header_quopri_len(self):
1473 eq = self.assertEqual
1474 hql = quopriMIME.header_quopri_len
1475 enc = quopriMIME.header_encode
1476 for s in ('hello', 'h@e@l@l@o@'):
1477 # Empty charset and no line-endings. 7 == RFC chrome
1478 eq(hql(s), len(enc(s, charset='', eol=''))-7)
1479 for c in self.hlit:
1480 eq(hql(c), 1)
1481 for c in self.hnon:
1482 eq(hql(c), 3)
1483
1484 def test_body_quopri_len(self):
1485 eq = self.assertEqual
1486 bql = quopriMIME.body_quopri_len
1487 for c in self.blit:
1488 eq(bql(c), 1)
1489 for c in self.bnon:
1490 eq(bql(c), 3)
1491
1492 def test_quote_unquote_idempotent(self):
1493 for x in range(256):
1494 c = chr(x)
1495 self.assertEqual(quopriMIME.unquote(quopriMIME.quote(c)), c)
1496
1497 def test_header_encode(self):
1498 eq = self.assertEqual
1499 he = quopriMIME.header_encode
1500 eq(he('hello'), '=?iso-8859-1?q?hello?=')
1501 eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
1502 # Test the charset option
1503 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
1504 # Test the keep_eols flag
1505 eq(he('hello\nworld', keep_eols=1), '=?iso-8859-1?q?hello=0Aworld?=')
1506 # Test a non-ASCII character
Jack Jansen1476c272002-04-23 10:52:44 +00001507 eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001508 # Test the maxlinelen argument
1509 eq(he('xxxx ' * 20, maxlinelen=40), """\
1510=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
1511 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
1512 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
1513 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
1514 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
1515 # Test the eol argument
1516 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1517=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
1518 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
1519 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
1520 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
1521 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
1522
1523 def test_decode(self):
1524 eq = self.assertEqual
1525 eq(quopriMIME.decode(''), '')
1526 eq(quopriMIME.decode('hello'), 'hello')
1527 eq(quopriMIME.decode('hello', 'X'), 'hello')
1528 eq(quopriMIME.decode('hello\nworld', 'X'), 'helloXworld')
1529
1530 def test_encode(self):
1531 eq = self.assertEqual
1532 eq(quopriMIME.encode(''), '')
1533 eq(quopriMIME.encode('hello'), 'hello')
1534 # Test the binary flag
1535 eq(quopriMIME.encode('hello\r\nworld'), 'hello\nworld')
1536 eq(quopriMIME.encode('hello\r\nworld', 0), 'hello\nworld')
1537 # Test the maxlinelen arg
1538 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40), """\
1539xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
1540 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
1541x xxxx xxxx xxxx xxxx=20""")
1542 # Test the eol argument
1543 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1544xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
1545 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
1546x xxxx xxxx xxxx xxxx=20""")
1547 eq(quopriMIME.encode("""\
1548one line
1549
1550two line"""), """\
1551one line
1552
1553two line""")
Barry Warsaw16f90552002-04-16 05:06:42 +00001554
Barry Warsaw409a4c02002-04-10 21:01:31 +00001555
1556
1557# Test the Charset class
1558class TestCharset(unittest.TestCase):
1559 def test_idempotent(self):
1560 eq = self.assertEqual
1561 # Make sure us-ascii = no Unicode conversion
1562 c = Charset('us-ascii')
1563 s = 'Hello World!'
1564 sp = c.to_splittable(s)
1565 eq(s, c.from_splittable(sp))
1566 # test 8-bit idempotency with us-ascii
1567 s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
1568 sp = c.to_splittable(s)
1569 eq(s, c.from_splittable(sp))
1570
1571
1572
1573# Test multilingual MIME headers.
1574class TestHeader(unittest.TestCase):
1575 def test_simple(self):
1576 eq = self.assertEqual
1577 h = Header('Hello World!')
1578 eq(h.encode(), 'Hello World!')
1579 h.append('Goodbye World!')
1580 eq(h.encode(), 'Hello World! Goodbye World!')
1581
1582 def test_header_needs_no_decoding(self):
1583 h = 'no decoding needed'
1584 self.assertEqual(decode_header(h), [(h, None)])
1585
1586 def test_long(self):
1587 h = Header("I am the very model of a modern Major-General; I've information vegetable, animal, and mineral; I know the kings of England, and I quote the fights historical from Marathon to Waterloo, in order categorical; I'm very well acquainted, too, with matters mathematical; I understand equations, both the simple and quadratical; about binomial theorem I'm teeming with a lot o' news, with many cheerful facts about the square of the hypotenuse.",
1588 maxlinelen=76)
1589 for l in h.encode().split('\n '):
1590 self.failUnless(len(l) <= 76)
1591
1592 def test_multilingual(self):
1593 eq = self.assertEqual
1594 g = Charset("iso-8859-1")
1595 cz = Charset("iso-8859-2")
1596 utf8 = Charset("utf-8")
1597 g_head = "Die Mieter treten hier ein werden mit einem Foerderband komfortabel den Korridor entlang, an s\xfcdl\xfcndischen Wandgem\xe4lden vorbei, gegen die rotierenden Klingen bef\xf6rdert. "
1598 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
1599 utf8_head = u"\u6b63\u78ba\u306b\u8a00\u3046\u3068\u7ffb\u8a33\u306f\u3055\u308c\u3066\u3044\u307e\u305b\u3093\u3002\u4e00\u90e8\u306f\u30c9\u30a4\u30c4\u8a9e\u3067\u3059\u304c\u3001\u3042\u3068\u306f\u3067\u305f\u3089\u3081\u3067\u3059\u3002\u5b9f\u969b\u306b\u306f\u300cWenn ist das Nunstuck git und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt gersput.\u300d\u3068\u8a00\u3063\u3066\u3044\u307e\u3059\u3002".encode("utf-8")
1600 h = Header(g_head, g)
1601 h.append(cz_head, cz)
1602 h.append(utf8_head, utf8)
1603 enc = h.encode()
1604 eq(enc, """=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
1605 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
1606 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
1607 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
1608 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
1609 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
1610 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
1611 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
1612 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
1613 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
1614 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
1615 =?utf-8?b?cyBOdW5zdHVjayBnaXQgdW5k?=
1616 =?utf-8?b?IFNsb3Rlcm1leWVyPyBKYSEgQmVpaGVyaHVuZCBkYXMgT2Rl?=
1617 =?utf-8?b?ciBkaWUgRmxpcHBlcndhbGR0?=
1618 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""")
1619 eq(decode_header(enc),
1620 [(g_head, "iso-8859-1"), (cz_head, "iso-8859-2"),
1621 (utf8_head, "utf-8")])
1622
Barry Warsawe0d85c82002-05-19 23:52:54 +00001623 def test_explicit_maxlinelen(self):
1624 eq = self.assertEqual
1625 hstr = 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
1626 h = Header(hstr)
1627 eq(h.encode(), '''\
1628A very long line that must get split to something other than at the 76th cha
1629 racter boundary to test the non-default behavior''')
1630 h = Header(hstr, header_name='Subject')
1631 eq(h.encode(), '''\
1632A very long line that must get split to something other than at the
1633 76th character boundary to test the non-default behavior''')
1634 h = Header(hstr, maxlinelen=1024, header_name='Subject')
1635 eq(h.encode(), hstr)
1636
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001637
1638
Barry Warsawc9ad32c2002-04-15 22:14:06 +00001639def _testclasses():
1640 mod = sys.modules[__name__]
1641 return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
1642
1643
Barry Warsaw41075852001-09-23 03:18:13 +00001644def suite():
1645 suite = unittest.TestSuite()
Barry Warsawc9ad32c2002-04-15 22:14:06 +00001646 for testclass in _testclasses():
1647 suite.addTest(unittest.makeSuite(testclass))
Barry Warsaw41075852001-09-23 03:18:13 +00001648 return suite
1649
1650
Barry Warsawc9ad32c2002-04-15 22:14:06 +00001651def test_main():
1652 for testclass in _testclasses():
1653 test_support.run_unittest(testclass)
1654
1655
Barry Warsaw08a534d2001-10-04 17:58:50 +00001656
Guido van Rossum78f0dd32001-12-07 21:07:08 +00001657if __name__ == '__main__':
Barry Warsaw409a4c02002-04-10 21:01:31 +00001658 unittest.main(defaultTest='suite')