blob: 132224649202246ab499cfd1596b99dbd514369e [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
10from types import StringType
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
Guido van Rossum78f0dd32001-12-07 21:07:08 +000032from test_support import findfile, __file__ as test_support_file
Barry Warsaw41075852001-09-23 03:18:13 +000033
34NL = '\n'
35EMPTYSTRING = ''
Barry Warsaw07227d12001-10-17 20:52:26 +000036SPACE = ' '
Barry Warsaw41075852001-09-23 03:18:13 +000037
Barry Warsaw409a4c02002-04-10 21:01:31 +000038# We don't care about DeprecationWarnings
39warnings.filterwarnings('ignore', '', DeprecationWarning, __name__)
40
Barry Warsaw41075852001-09-23 03:18:13 +000041
Barry Warsaw08a534d2001-10-04 17:58:50 +000042
Barry Warsaw41075852001-09-23 03:18:13 +000043def openfile(filename):
Guido van Rossum78f0dd32001-12-07 21:07:08 +000044 path = os.path.join(os.path.dirname(test_support_file), 'data', filename)
Barry Warsaw41075852001-09-23 03:18:13 +000045 return open(path)
46
47
Barry Warsaw08a534d2001-10-04 17:58:50 +000048
Barry Warsaw41075852001-09-23 03:18:13 +000049# Base test class
50class TestEmailBase(unittest.TestCase):
51 def _msgobj(self, filename):
Barry Warsaw409a4c02002-04-10 21:01:31 +000052 fp = openfile(findfile(filename))
Barry Warsaw41075852001-09-23 03:18:13 +000053 try:
Barry Warsaw65279d02001-09-26 05:47:08 +000054 msg = email.message_from_file(fp)
Barry Warsaw41075852001-09-23 03:18:13 +000055 finally:
56 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +000057 return msg
Barry Warsaw41075852001-09-23 03:18:13 +000058
59
Barry Warsaw08a534d2001-10-04 17:58:50 +000060
Barry Warsaw41075852001-09-23 03:18:13 +000061# Test various aspects of the Message class's API
62class TestMessageAPI(TestEmailBase):
Barry Warsaw2f6a0b02001-10-09 15:49:35 +000063 def test_get_all(self):
64 eq = self.assertEqual
65 msg = self._msgobj('msg_20.txt')
66 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
67 eq(msg.get_all('xx', 'n/a'), 'n/a')
68
Barry Warsaw409a4c02002-04-10 21:01:31 +000069 def test_getset_charset(self):
70 eq = self.assertEqual
71 msg = Message()
72 eq(msg.get_charset(), None)
73 charset = Charset('iso-8859-1')
74 msg.set_charset(charset)
75 eq(msg['mime-version'], '1.0')
76 eq(msg.get_type(), 'text/plain')
77 eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
78 eq(msg.get_param('charset'), 'iso-8859-1')
79 eq(msg['content-transfer-encoding'], 'quoted-printable')
80 eq(msg.get_charset().input_charset, 'iso-8859-1')
81 # Remove the charset
82 msg.set_charset(None)
83 eq(msg.get_charset(), None)
84 eq(msg['content-type'], 'text/plain')
85 # Try adding a charset when there's already MIME headers present
86 msg = Message()
87 msg['MIME-Version'] = '2.0'
88 msg['Content-Type'] = 'text/x-weird'
89 msg['Content-Transfer-Encoding'] = 'quinted-puntable'
90 msg.set_charset(charset)
91 eq(msg['mime-version'], '2.0')
92 eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
93 eq(msg['content-transfer-encoding'], 'quinted-puntable')
94
95 def test_set_charset_from_string(self):
96 eq = self.assertEqual
97 msg = Message()
98 msg.set_charset('us-ascii')
99 eq(msg.get_charset().input_charset, 'us-ascii')
100 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
101
102 def test_set_payload_with_charset(self):
103 msg = Message()
104 charset = Charset('iso-8859-1')
105 msg.set_payload('This is a string payload', charset)
106 self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
107
Barry Warsaw41075852001-09-23 03:18:13 +0000108 def test_get_charsets(self):
109 eq = self.assertEqual
Tim Peters527e64f2001-10-04 05:36:56 +0000110
Barry Warsaw65279d02001-09-26 05:47:08 +0000111 msg = self._msgobj('msg_08.txt')
112 charsets = msg.get_charsets()
113 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000114
Barry Warsaw65279d02001-09-26 05:47:08 +0000115 msg = self._msgobj('msg_09.txt')
116 charsets = msg.get_charsets('dingbat')
117 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
118 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000119
Barry Warsaw65279d02001-09-26 05:47:08 +0000120 msg = self._msgobj('msg_12.txt')
121 charsets = msg.get_charsets()
122 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
123 'iso-8859-3', 'us-ascii', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000124
125 def test_get_filename(self):
126 eq = self.assertEqual
127
Barry Warsaw65279d02001-09-26 05:47:08 +0000128 msg = self._msgobj('msg_04.txt')
129 filenames = [p.get_filename() for p in msg.get_payload()]
Barry Warsaw41075852001-09-23 03:18:13 +0000130 eq(filenames, ['msg.txt', 'msg.txt'])
131
Barry Warsaw65279d02001-09-26 05:47:08 +0000132 msg = self._msgobj('msg_07.txt')
133 subpart = msg.get_payload(1)
Barry Warsaw41075852001-09-23 03:18:13 +0000134 eq(subpart.get_filename(), 'dingusfish.gif')
135
136 def test_get_boundary(self):
137 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000138 msg = self._msgobj('msg_07.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000139 # No quotes!
Barry Warsaw65279d02001-09-26 05:47:08 +0000140 eq(msg.get_boundary(), 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000141
142 def test_set_boundary(self):
143 eq = self.assertEqual
144 # This one has no existing boundary parameter, but the Content-Type:
145 # header appears fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000146 msg = self._msgobj('msg_01.txt')
147 msg.set_boundary('BOUNDARY')
148 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000149 eq(header.lower(), 'content-type')
150 eq(value, 'text/plain; charset=us-ascii; boundary="BOUNDARY"')
151 # This one has a Content-Type: header, with a boundary, stuck in the
152 # middle of its headers. Make sure the order is preserved; it should
153 # be fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000154 msg = self._msgobj('msg_04.txt')
155 msg.set_boundary('BOUNDARY')
156 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000157 eq(header.lower(), 'content-type')
158 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
159 # And this one has no Content-Type: header at all.
Barry Warsaw65279d02001-09-26 05:47:08 +0000160 msg = self._msgobj('msg_03.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000161 self.assertRaises(Errors.HeaderParseError,
Barry Warsaw65279d02001-09-26 05:47:08 +0000162 msg.set_boundary, 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000163
164 def test_get_decoded_payload(self):
165 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000166 msg = self._msgobj('msg_10.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000167 # The outer message is a multipart
Barry Warsaw65279d02001-09-26 05:47:08 +0000168 eq(msg.get_payload(decode=1), None)
Barry Warsaw41075852001-09-23 03:18:13 +0000169 # Subpart 1 is 7bit encoded
Barry Warsaw65279d02001-09-26 05:47:08 +0000170 eq(msg.get_payload(0).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000171 'This is a 7bit encoded message.\n')
172 # Subpart 2 is quopri
Barry Warsaw65279d02001-09-26 05:47:08 +0000173 eq(msg.get_payload(1).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000174 '\xa1This is a Quoted Printable encoded message!\n')
175 # Subpart 3 is base64
Barry Warsaw65279d02001-09-26 05:47:08 +0000176 eq(msg.get_payload(2).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000177 'This is a Base64 encoded message.')
178 # Subpart 4 has no Content-Transfer-Encoding: header.
Barry Warsaw65279d02001-09-26 05:47:08 +0000179 eq(msg.get_payload(3).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000180 'This has no Content-Transfer-Encoding: header.\n')
181
182 def test_decoded_generator(self):
183 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000184 msg = self._msgobj('msg_07.txt')
185 fp = openfile('msg_17.txt')
186 try:
187 text = fp.read()
188 finally:
189 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +0000190 s = StringIO()
191 g = DecodedGenerator(s)
Barry Warsaw65279d02001-09-26 05:47:08 +0000192 g(msg)
193 eq(s.getvalue(), text)
Barry Warsaw41075852001-09-23 03:18:13 +0000194
195 def test__contains__(self):
196 msg = Message()
197 msg['From'] = 'Me'
198 msg['to'] = 'You'
199 # Check for case insensitivity
200 self.failUnless('from' in msg)
201 self.failUnless('From' in msg)
202 self.failUnless('FROM' in msg)
203 self.failUnless('to' in msg)
204 self.failUnless('To' in msg)
205 self.failUnless('TO' in msg)
206
207 def test_as_string(self):
208 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000209 msg = self._msgobj('msg_01.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000210 fp = openfile('msg_01.txt')
211 try:
212 text = fp.read()
213 finally:
214 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000215 eq(text, msg.as_string())
216 fullrepr = str(msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000217 lines = fullrepr.split('\n')
218 self.failUnless(lines[0].startswith('From '))
219 eq(text, NL.join(lines[1:]))
220
221 def test_bad_param(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000222 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000223 self.assertEqual(msg.get_param('baz'), '')
224
225 def test_missing_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000226 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000227 self.assertEqual(msg.get_filename(), None)
228
229 def test_bogus_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000230 msg = email.message_from_string(
231 "Content-Disposition: blarg; filename\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000232 self.assertEqual(msg.get_filename(), '')
Tim Peters527e64f2001-10-04 05:36:56 +0000233
Barry Warsaw41075852001-09-23 03:18:13 +0000234 def test_missing_boundary(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000235 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000236 self.assertEqual(msg.get_boundary(), None)
237
Barry Warsaw65279d02001-09-26 05:47:08 +0000238 def test_get_params(self):
239 eq = self.assertEqual
240 msg = email.message_from_string(
241 'X-Header: foo=one; bar=two; baz=three\n')
242 eq(msg.get_params(header='x-header'),
243 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
244 msg = email.message_from_string(
245 'X-Header: foo; bar=one; baz=two\n')
246 eq(msg.get_params(header='x-header'),
247 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
248 eq(msg.get_params(), None)
249 msg = email.message_from_string(
250 'X-Header: foo; bar="one"; baz=two\n')
251 eq(msg.get_params(header='x-header'),
252 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
253
Barry Warsaw409a4c02002-04-10 21:01:31 +0000254 def test_get_param_liberal(self):
255 msg = Message()
256 msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
257 self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
258
Barry Warsaw65279d02001-09-26 05:47:08 +0000259 def test_get_param(self):
260 eq = self.assertEqual
261 msg = email.message_from_string(
262 "X-Header: foo=one; bar=two; baz=three\n")
263 eq(msg.get_param('bar', header='x-header'), 'two')
264 eq(msg.get_param('quuz', header='x-header'), None)
265 eq(msg.get_param('quuz'), None)
266 msg = email.message_from_string(
267 'X-Header: foo; bar="one"; baz=two\n')
268 eq(msg.get_param('foo', header='x-header'), '')
269 eq(msg.get_param('bar', header='x-header'), 'one')
270 eq(msg.get_param('baz', header='x-header'), 'two')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000271 # XXX: We are not RFC-2045 compliant! We cannot parse:
272 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
273 # msg.get_param("weird")
274 # yet.
Barry Warsaw65279d02001-09-26 05:47:08 +0000275
Barry Warsaw2539cf52001-10-25 22:43:46 +0000276 def test_get_param_funky_continuation_lines(self):
277 msg = self._msgobj('msg_22.txt')
278 self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
279
Barry Warsaw65279d02001-09-26 05:47:08 +0000280 def test_has_key(self):
281 msg = email.message_from_string('Header: exists')
282 self.failUnless(msg.has_key('header'))
283 self.failUnless(msg.has_key('Header'))
284 self.failUnless(msg.has_key('HEADER'))
285 self.failIf(msg.has_key('headeri'))
286
Barry Warsaw409a4c02002-04-10 21:01:31 +0000287 def test_set_param(self):
288 eq = self.assertEqual
289 msg = Message()
290 msg.set_param('charset', 'iso-2022-jp')
291 eq(msg.get_param('charset'), 'iso-2022-jp')
292 msg.set_param('importance', 'high value')
293 eq(msg.get_param('importance'), 'high value')
294 eq(msg.get_param('importance', unquote=0), '"high value"')
295 eq(msg.get_params(), [('text/plain', ''),
296 ('charset', 'iso-2022-jp'),
297 ('importance', 'high value')])
298 eq(msg.get_params(unquote=0), [('text/plain', ''),
299 ('charset', '"iso-2022-jp"'),
300 ('importance', '"high value"')])
301 msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
302 eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
Barry Warsaw41075852001-09-23 03:18:13 +0000303
Barry Warsaw409a4c02002-04-10 21:01:31 +0000304 def test_del_param(self):
305 eq = self.assertEqual
306 msg = self._msgobj('msg_05.txt')
307 eq(msg.get_params(),
308 [('multipart/report', ''), ('report-type', 'delivery-status'),
309 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
310 old_val = msg.get_param("report-type")
311 msg.del_param("report-type")
312 eq(msg.get_params(),
313 [('multipart/report', ''),
314 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
315 msg.set_param("report-type", old_val)
316 eq(msg.get_params(),
317 [('multipart/report', ''),
318 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
319 ('report-type', old_val)])
320
321 def test_set_type(self):
322 eq = self.assertEqual
323 msg = Message()
324 self.assertRaises(ValueError, msg.set_type, 'text')
325 msg.set_type('text/plain')
326 eq(msg['content-type'], 'text/plain')
327 msg.set_param('charset', 'us-ascii')
328 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
329 msg.set_type('text/html')
330 eq(msg['content-type'], 'text/html; charset="us-ascii"')
331
332
Barry Warsaw08a534d2001-10-04 17:58:50 +0000333
Barry Warsaw41075852001-09-23 03:18:13 +0000334# Test the email.Encoders module
335class TestEncoders(unittest.TestCase):
336 def test_encode_noop(self):
337 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000338 msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
Barry Warsaw41075852001-09-23 03:18:13 +0000339 eq(msg.get_payload(), 'hello world\n')
Barry Warsaw41075852001-09-23 03:18:13 +0000340
341 def test_encode_7bit(self):
342 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000343 msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000344 eq(msg.get_payload(), 'hello world\n')
345 eq(msg['content-transfer-encoding'], '7bit')
Barry Warsaw65279d02001-09-26 05:47:08 +0000346 msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000347 eq(msg.get_payload(), 'hello \x7f world\n')
348 eq(msg['content-transfer-encoding'], '7bit')
349
350 def test_encode_8bit(self):
351 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000352 msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000353 eq(msg.get_payload(), 'hello \x80 world\n')
354 eq(msg['content-transfer-encoding'], '8bit')
355
Barry Warsaw409a4c02002-04-10 21:01:31 +0000356 def test_encode_empty_payload(self):
357 eq = self.assertEqual
358 msg = Message()
359 msg.set_charset('us-ascii')
360 eq(msg['content-transfer-encoding'], '7bit')
361
Barry Warsaw41075852001-09-23 03:18:13 +0000362 def test_encode_base64(self):
363 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000364 msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
Barry Warsaw41075852001-09-23 03:18:13 +0000365 eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
366 eq(msg['content-transfer-encoding'], 'base64')
367
368 def test_encode_quoted_printable(self):
369 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000370 msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
Barry Warsaw41075852001-09-23 03:18:13 +0000371 eq(msg.get_payload(), 'hello=20world\n')
372 eq(msg['content-transfer-encoding'], 'quoted-printable')
373
Barry Warsaw409a4c02002-04-10 21:01:31 +0000374 def test_default_cte(self):
375 eq = self.assertEqual
376 msg = MIMEText('hello world')
377 eq(msg['content-transfer-encoding'], '7bit')
378
379 def test_default_cte(self):
380 eq = self.assertEqual
381 # With no explicit _charset its us-ascii, and all are 7-bit
382 msg = MIMEText('hello world')
383 eq(msg['content-transfer-encoding'], '7bit')
384 # Similar, but with 8-bit data
385 msg = MIMEText('hello \xf8 world')
386 eq(msg['content-transfer-encoding'], '8bit')
387 # And now with a different charset
388 msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
389 eq(msg['content-transfer-encoding'], 'quoted-printable')
390
Barry Warsaw41075852001-09-23 03:18:13 +0000391
Barry Warsaw08a534d2001-10-04 17:58:50 +0000392
393# Test long header wrapping
Barry Warsaw41075852001-09-23 03:18:13 +0000394class TestLongHeaders(unittest.TestCase):
395 def test_header_splitter(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000396 msg = MIMEText('')
Barry Warsaw41075852001-09-23 03:18:13 +0000397 # It'd be great if we could use add_header() here, but that doesn't
398 # guarantee an order of the parameters.
399 msg['X-Foobar-Spoink-Defrobnit'] = (
400 'wasnipoop; giraffes="very-long-necked-animals"; '
401 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
402 sfp = StringIO()
403 g = Generator(sfp)
404 g(msg)
Barry Warsaw409a4c02002-04-10 21:01:31 +0000405 self.assertEqual(sfp.getvalue(), '''\
406Content-Type: text/plain; charset="us-ascii"
407MIME-Version: 1.0
408Content-Transfer-Encoding: 7bit
409X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
410 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
411
412''')
Barry Warsaw41075852001-09-23 03:18:13 +0000413
Barry Warsaw07227d12001-10-17 20:52:26 +0000414 def test_no_semis_header_splitter(self):
415 msg = Message()
416 msg['From'] = 'test@dom.ain'
417 refparts = []
418 for i in range(10):
419 refparts.append('<%d@dom.ain>' % i)
420 msg['References'] = SPACE.join(refparts)
421 msg.set_payload('Test')
422 sfp = StringIO()
423 g = Generator(sfp)
424 g(msg)
425 self.assertEqual(sfp.getvalue(), """\
426From: test@dom.ain
427References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
Tim Peterse0c446b2001-10-18 21:57:37 +0000428\t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
Barry Warsaw07227d12001-10-17 20:52:26 +0000429
430Test""")
431
432 def test_no_split_long_header(self):
433 msg = Message()
434 msg['From'] = 'test@dom.ain'
435 refparts = []
436 msg['References'] = 'x' * 80
437 msg.set_payload('Test')
438 sfp = StringIO()
439 g = Generator(sfp)
440 g(msg)
441 self.assertEqual(sfp.getvalue(), """\
442From: test@dom.ain
443References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
444
445Test""")
446
Barry Warsaw409a4c02002-04-10 21:01:31 +0000447 def test_splitting_multiple_long_lines(self):
448 msg = Message()
449 msg['Received'] = """\
450from 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)
451 from 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)
452 from 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)
453"""
454 self.assertEqual(msg.as_string(), """\
455Received: from babylon.socal-raves.org (localhost [127.0.0.1]);
456 by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
457 for <mailman-admin@babylon.socal-raves.org>;
458 Sat, 2 Feb 2002 17:00:06 -0800 (PST)
459 from babylon.socal-raves.org (localhost [127.0.0.1]);
460 by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
461 for <mailman-admin@babylon.socal-raves.org>;
462 Sat, 2 Feb 2002 17:00:06 -0800 (PST)
463 from babylon.socal-raves.org (localhost [127.0.0.1]);
464 by babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
465 for <mailman-admin@babylon.socal-raves.org>;
466 Sat, 2 Feb 2002 17:00:06 -0800 (PST)
467
468
469""")
470
Barry Warsaw41075852001-09-23 03:18:13 +0000471
Barry Warsaw08a534d2001-10-04 17:58:50 +0000472
473# Test mangling of "From " lines in the body of a message
Barry Warsaw41075852001-09-23 03:18:13 +0000474class TestFromMangling(unittest.TestCase):
475 def setUp(self):
476 self.msg = Message()
477 self.msg['From'] = 'aaa@bbb.org'
478 self.msg.add_payload("""\
479From the desk of A.A.A.:
480Blah blah blah
481""")
482
483 def test_mangled_from(self):
484 s = StringIO()
485 g = Generator(s, mangle_from_=1)
486 g(self.msg)
487 self.assertEqual(s.getvalue(), """\
488From: aaa@bbb.org
489
490>From the desk of A.A.A.:
491Blah blah blah
492""")
493
494 def test_dont_mangle_from(self):
495 s = StringIO()
496 g = Generator(s, mangle_from_=0)
497 g(self.msg)
498 self.assertEqual(s.getvalue(), """\
499From: aaa@bbb.org
500
501From the desk of A.A.A.:
502Blah blah blah
503""")
504
505
Barry Warsaw08a534d2001-10-04 17:58:50 +0000506
Barry Warsawfee435a2001-10-09 19:23:57 +0000507# Test the basic MIMEAudio class
508class TestMIMEAudio(unittest.TestCase):
509 def setUp(self):
510 # In Python, audiotest.au lives in Lib/test not Lib/test/data
511 fp = open(findfile('audiotest.au'))
512 try:
513 self._audiodata = fp.read()
514 finally:
515 fp.close()
516 self._au = MIMEAudio(self._audiodata)
517
518 def test_guess_minor_type(self):
519 self.assertEqual(self._au.get_type(), 'audio/basic')
520
521 def test_encoding(self):
522 payload = self._au.get_payload()
523 self.assertEqual(base64.decodestring(payload), self._audiodata)
524
525 def checkSetMinor(self):
526 au = MIMEAudio(self._audiodata, 'fish')
527 self.assertEqual(im.get_type(), 'audio/fish')
528
529 def test_custom_encoder(self):
530 eq = self.assertEqual
531 def encoder(msg):
532 orig = msg.get_payload()
533 msg.set_payload(0)
534 msg['Content-Transfer-Encoding'] = 'broken64'
535 au = MIMEAudio(self._audiodata, _encoder=encoder)
536 eq(au.get_payload(), 0)
537 eq(au['content-transfer-encoding'], 'broken64')
538
539 def test_add_header(self):
540 eq = self.assertEqual
541 unless = self.failUnless
542 self._au.add_header('Content-Disposition', 'attachment',
543 filename='audiotest.au')
544 eq(self._au['content-disposition'],
545 'attachment; filename="audiotest.au"')
546 eq(self._au.get_params(header='content-disposition'),
547 [('attachment', ''), ('filename', 'audiotest.au')])
548 eq(self._au.get_param('filename', header='content-disposition'),
549 'audiotest.au')
550 missing = []
551 eq(self._au.get_param('attachment', header='content-disposition'), '')
552 unless(self._au.get_param('foo', failobj=missing,
553 header='content-disposition') is missing)
554 # Try some missing stuff
555 unless(self._au.get_param('foobar', missing) is missing)
556 unless(self._au.get_param('attachment', missing,
557 header='foobar') is missing)
558
559
560
Barry Warsaw65279d02001-09-26 05:47:08 +0000561# Test the basic MIMEImage class
562class TestMIMEImage(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000563 def setUp(self):
564 fp = openfile('PyBanner048.gif')
565 try:
566 self._imgdata = fp.read()
567 finally:
568 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000569 self._im = MIMEImage(self._imgdata)
Barry Warsaw41075852001-09-23 03:18:13 +0000570
571 def test_guess_minor_type(self):
572 self.assertEqual(self._im.get_type(), 'image/gif')
573
574 def test_encoding(self):
575 payload = self._im.get_payload()
576 self.assertEqual(base64.decodestring(payload), self._imgdata)
577
578 def checkSetMinor(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000579 im = MIMEImage(self._imgdata, 'fish')
Barry Warsaw41075852001-09-23 03:18:13 +0000580 self.assertEqual(im.get_type(), 'image/fish')
581
582 def test_custom_encoder(self):
583 eq = self.assertEqual
584 def encoder(msg):
585 orig = msg.get_payload()
586 msg.set_payload(0)
587 msg['Content-Transfer-Encoding'] = 'broken64'
Barry Warsaw65279d02001-09-26 05:47:08 +0000588 im = MIMEImage(self._imgdata, _encoder=encoder)
Barry Warsaw41075852001-09-23 03:18:13 +0000589 eq(im.get_payload(), 0)
590 eq(im['content-transfer-encoding'], 'broken64')
591
592 def test_add_header(self):
593 eq = self.assertEqual
594 unless = self.failUnless
595 self._im.add_header('Content-Disposition', 'attachment',
596 filename='dingusfish.gif')
597 eq(self._im['content-disposition'],
598 'attachment; filename="dingusfish.gif"')
599 eq(self._im.get_params(header='content-disposition'),
Barry Warsaw65279d02001-09-26 05:47:08 +0000600 [('attachment', ''), ('filename', 'dingusfish.gif')])
Barry Warsaw41075852001-09-23 03:18:13 +0000601 eq(self._im.get_param('filename', header='content-disposition'),
602 'dingusfish.gif')
603 missing = []
Barry Warsaw65279d02001-09-26 05:47:08 +0000604 eq(self._im.get_param('attachment', header='content-disposition'), '')
605 unless(self._im.get_param('foo', failobj=missing,
Barry Warsaw41075852001-09-23 03:18:13 +0000606 header='content-disposition') is missing)
607 # Try some missing stuff
608 unless(self._im.get_param('foobar', missing) is missing)
609 unless(self._im.get_param('attachment', missing,
610 header='foobar') is missing)
611
612
Barry Warsaw08a534d2001-10-04 17:58:50 +0000613
Barry Warsaw65279d02001-09-26 05:47:08 +0000614# Test the basic MIMEText class
615class TestMIMEText(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000616 def setUp(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000617 self._msg = MIMEText('hello there')
Barry Warsaw41075852001-09-23 03:18:13 +0000618
619 def test_types(self):
620 eq = self.assertEqual
621 unless = self.failUnless
622 eq(self._msg.get_type(), 'text/plain')
623 eq(self._msg.get_param('charset'), 'us-ascii')
624 missing = []
625 unless(self._msg.get_param('foobar', missing) is missing)
626 unless(self._msg.get_param('charset', missing, header='foobar')
627 is missing)
628
629 def test_payload(self):
630 self.assertEqual(self._msg.get_payload(), 'hello there\n')
631 self.failUnless(not self._msg.is_multipart())
632
Barry Warsaw409a4c02002-04-10 21:01:31 +0000633 def test_charset(self):
634 eq = self.assertEqual
635 msg = MIMEText('hello there', _charset='us-ascii')
636 eq(msg.get_charset().input_charset, 'us-ascii')
637 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
638
Barry Warsaw41075852001-09-23 03:18:13 +0000639
Barry Warsaw08a534d2001-10-04 17:58:50 +0000640
641# Test a more complicated multipart/mixed type message
Barry Warsaw41075852001-09-23 03:18:13 +0000642class TestMultipartMixed(unittest.TestCase):
643 def setUp(self):
644 fp = openfile('PyBanner048.gif')
645 try:
646 data = fp.read()
647 finally:
648 fp.close()
649
650 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
Barry Warsaw65279d02001-09-26 05:47:08 +0000651 image = MIMEImage(data, name='dingusfish.gif')
Barry Warsaw41075852001-09-23 03:18:13 +0000652 image.add_header('content-disposition', 'attachment',
653 filename='dingusfish.gif')
Barry Warsaw65279d02001-09-26 05:47:08 +0000654 intro = MIMEText('''\
Barry Warsaw41075852001-09-23 03:18:13 +0000655Hi there,
656
657This is the dingus fish.
658''')
659 container.add_payload(intro)
660 container.add_payload(image)
661 container['From'] = 'Barry <barry@digicool.com>'
662 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
663 container['Subject'] = 'Here is your dingus fish'
Tim Peters527e64f2001-10-04 05:36:56 +0000664
Barry Warsaw41075852001-09-23 03:18:13 +0000665 now = 987809702.54848599
666 timetuple = time.localtime(now)
667 if timetuple[-1] == 0:
668 tzsecs = time.timezone
669 else:
670 tzsecs = time.altzone
671 if tzsecs > 0:
672 sign = '-'
673 else:
674 sign = '+'
675 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
676 container['Date'] = time.strftime(
677 '%a, %d %b %Y %H:%M:%S',
678 time.localtime(now)) + tzoffset
679 self._msg = container
680 self._im = image
681 self._txt = intro
682
683 def test_hierarchy(self):
684 # convenience
685 eq = self.assertEqual
686 unless = self.failUnless
687 raises = self.assertRaises
688 # tests
689 m = self._msg
690 unless(m.is_multipart())
691 eq(m.get_type(), 'multipart/mixed')
692 eq(len(m.get_payload()), 2)
693 raises(IndexError, m.get_payload, 2)
694 m0 = m.get_payload(0)
695 m1 = m.get_payload(1)
696 unless(m0 is self._txt)
697 unless(m1 is self._im)
698 eq(m.get_payload(), [m0, m1])
699 unless(not m0.is_multipart())
700 unless(not m1.is_multipart())
701
Barry Warsaw409a4c02002-04-10 21:01:31 +0000702 def test_no_parts_in_a_multipart(self):
703 outer = MIMEBase('multipart', 'mixed')
704 outer['Subject'] = 'A subject'
705 outer['To'] = 'aperson@dom.ain'
706 outer['From'] = 'bperson@dom.ain'
707 outer.preamble = ''
708 outer.epilogue = ''
709 outer.set_boundary('BOUNDARY')
710 msg = MIMEText('hello world')
711 self.assertEqual(outer.as_string(), '''\
712Content-Type: multipart/mixed; boundary="BOUNDARY"
713MIME-Version: 1.0
714Subject: A subject
715To: aperson@dom.ain
716From: bperson@dom.ain
717
718--BOUNDARY
719
720
721--BOUNDARY--
722''')
723
724 def test_one_part_in_a_multipart(self):
725 outer = MIMEBase('multipart', 'mixed')
726 outer['Subject'] = 'A subject'
727 outer['To'] = 'aperson@dom.ain'
728 outer['From'] = 'bperson@dom.ain'
729 outer.preamble = ''
730 outer.epilogue = ''
731 outer.set_boundary('BOUNDARY')
732 msg = MIMEText('hello world')
733 outer.attach(msg)
734 self.assertEqual(outer.as_string(), '''\
735Content-Type: multipart/mixed; boundary="BOUNDARY"
736MIME-Version: 1.0
737Subject: A subject
738To: aperson@dom.ain
739From: bperson@dom.ain
740
741--BOUNDARY
742Content-Type: text/plain; charset="us-ascii"
743MIME-Version: 1.0
744Content-Transfer-Encoding: 7bit
745
746hello world
747
748--BOUNDARY--
749''')
750
751 def test_seq_parts_in_a_multipart(self):
752 outer = MIMEBase('multipart', 'mixed')
753 outer['Subject'] = 'A subject'
754 outer['To'] = 'aperson@dom.ain'
755 outer['From'] = 'bperson@dom.ain'
756 outer.preamble = ''
757 outer.epilogue = ''
758 msg = MIMEText('hello world')
759 outer.attach(msg)
760 outer.set_boundary('BOUNDARY')
761 self.assertEqual(outer.as_string(), '''\
762Content-Type: multipart/mixed; boundary="BOUNDARY"
763MIME-Version: 1.0
764Subject: A subject
765To: aperson@dom.ain
766From: bperson@dom.ain
767
768--BOUNDARY
769Content-Type: text/plain; charset="us-ascii"
770MIME-Version: 1.0
771Content-Transfer-Encoding: 7bit
772
773hello world
774
775--BOUNDARY--
776''')
777
Barry Warsaw41075852001-09-23 03:18:13 +0000778
Barry Warsaw08a534d2001-10-04 17:58:50 +0000779
780# Test some badly formatted messages
Barry Warsaw41075852001-09-23 03:18:13 +0000781class TestNonConformant(TestEmailBase):
782 def test_parse_missing_minor_type(self):
783 eq = self.assertEqual
784 msg = self._msgobj('msg_14.txt')
785 eq(msg.get_type(), 'text')
786 eq(msg.get_main_type(), 'text')
787 self.failUnless(msg.get_subtype() is None)
788
789 def test_bogus_boundary(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +0000790 fp = openfile(findfile('msg_15.txt'))
Barry Warsaw41075852001-09-23 03:18:13 +0000791 try:
792 data = fp.read()
793 finally:
794 fp.close()
795 p = Parser()
796 # Note, under a future non-strict parsing mode, this would parse the
797 # message into the intended message tree.
798 self.assertRaises(Errors.BoundaryError, p.parsestr, data)
799
Barry Warsaw409a4c02002-04-10 21:01:31 +0000800 def test_multipart_no_boundary(self):
801 fp = openfile(findfile('msg_25.txt'))
802 self.assertRaises(Errors.BoundaryError, email.message_from_file, fp)
803
Barry Warsaw41075852001-09-23 03:18:13 +0000804
Barry Warsaw08a534d2001-10-04 17:58:50 +0000805
806# Test RFC 2047 header encoding and decoding
Barry Warsaw41075852001-09-23 03:18:13 +0000807class TestRFC2047(unittest.TestCase):
808 def test_iso_8859_1(self):
809 eq = self.assertEqual
810 s = '=?iso-8859-1?q?this=20is=20some=20text?='
811 eq(Utils.decode(s), 'this is some text')
812 s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
Barry Warsaw409a4c02002-04-10 21:01:31 +0000813 eq(Utils.decode(s), u'Keld J\xf8rn Simonsen')
Barry Warsaw41075852001-09-23 03:18:13 +0000814 s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
815 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
816 eq(Utils.decode(s), 'If you can read this you understand the example.')
817 s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
818 eq(Utils.decode(s),
819 u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
820 s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
Barry Warsaw409a4c02002-04-10 21:01:31 +0000821 eq(Utils.decode(s), u'this issome text')
822 s = '=?iso-8859-1?q?this=20is_?= =?iso-8859-1?q?some=20text?='
Barry Warsaw41075852001-09-23 03:18:13 +0000823 eq(Utils.decode(s), u'this is some text')
824
825 def test_encode_header(self):
826 eq = self.assertEqual
827 s = 'this is some text'
828 eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
829 s = 'Keld_J\xf8rn_Simonsen'
830 eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
831 s1 = 'If you can read this yo'
832 s2 = 'u understand the example.'
833 eq(Utils.encode(s1, encoding='b'),
834 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
835 eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
836 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
837
838
Barry Warsaw08a534d2001-10-04 17:58:50 +0000839
840# Test the MIMEMessage class
Barry Warsaw65279d02001-09-26 05:47:08 +0000841class TestMIMEMessage(TestEmailBase):
Barry Warsaw41075852001-09-23 03:18:13 +0000842 def setUp(self):
843 fp = openfile('msg_11.txt')
844 self._text = fp.read()
845 fp.close()
846
847 def test_type_error(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000848 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
Barry Warsaw41075852001-09-23 03:18:13 +0000849
850 def test_valid_argument(self):
851 eq = self.assertEqual
852 subject = 'A sub-message'
853 m = Message()
854 m['Subject'] = subject
Barry Warsaw65279d02001-09-26 05:47:08 +0000855 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000856 eq(r.get_type(), 'message/rfc822')
857 self.failUnless(r.get_payload() is m)
858 eq(r.get_payload()['subject'], subject)
859
860 def test_generate(self):
861 # First craft the message to be encapsulated
862 m = Message()
863 m['Subject'] = 'An enclosed message'
864 m.add_payload('Here is the body of the message.\n')
Barry Warsaw65279d02001-09-26 05:47:08 +0000865 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000866 r['Subject'] = 'The enclosing message'
867 s = StringIO()
868 g = Generator(s)
869 g(r)
Barry Warsaw65279d02001-09-26 05:47:08 +0000870 self.assertEqual(s.getvalue(), """\
871Content-Type: message/rfc822
872MIME-Version: 1.0
873Subject: The enclosing message
874
875Subject: An enclosed message
876
877Here is the body of the message.
878""")
879
880 def test_parse_message_rfc822(self):
881 eq = self.assertEqual
882 msg = self._msgobj('msg_11.txt')
883 eq(msg.get_type(), 'message/rfc822')
884 eq(len(msg.get_payload()), 1)
885 submsg = msg.get_payload()
886 self.failUnless(isinstance(submsg, Message))
887 eq(submsg['subject'], 'An enclosed message')
888 eq(submsg.get_payload(), 'Here is the body of the message.\n')
889
890 def test_dsn(self):
891 eq = self.assertEqual
892 unless = self.failUnless
893 # msg 16 is a Delivery Status Notification, see RFC XXXX
894 msg = self._msgobj('msg_16.txt')
895 eq(msg.get_type(), 'multipart/report')
896 unless(msg.is_multipart())
897 eq(len(msg.get_payload()), 3)
898 # Subpart 1 is a text/plain, human readable section
899 subpart = msg.get_payload(0)
900 eq(subpart.get_type(), 'text/plain')
901 eq(subpart.get_payload(), """\
902This report relates to a message you sent with the following header fields:
903
904 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
905 Date: Sun, 23 Sep 2001 20:10:55 -0700
906 From: "Ian T. Henry" <henryi@oxy.edu>
907 To: SoCal Raves <scr@socal-raves.org>
908 Subject: [scr] yeah for Ians!!
909
910Your message cannot be delivered to the following recipients:
911
912 Recipient address: jangel1@cougar.noc.ucla.edu
913 Reason: recipient reached disk quota
914
915""")
916 # Subpart 2 contains the machine parsable DSN information. It
917 # consists of two blocks of headers, represented by two nested Message
918 # objects.
919 subpart = msg.get_payload(1)
920 eq(subpart.get_type(), 'message/delivery-status')
921 eq(len(subpart.get_payload()), 2)
922 # message/delivery-status should treat each block as a bunch of
923 # headers, i.e. a bunch of Message objects.
924 dsn1 = subpart.get_payload(0)
925 unless(isinstance(dsn1, Message))
926 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
927 eq(dsn1.get_param('dns', header='reporting-mta'), '')
928 # Try a missing one <wink>
929 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
930 dsn2 = subpart.get_payload(1)
931 unless(isinstance(dsn2, Message))
932 eq(dsn2['action'], 'failed')
933 eq(dsn2.get_params(header='original-recipient'),
934 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
935 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
936 # Subpart 3 is the original message
937 subpart = msg.get_payload(2)
938 eq(subpart.get_type(), 'message/rfc822')
939 subsubpart = subpart.get_payload()
940 unless(isinstance(subsubpart, Message))
941 eq(subsubpart.get_type(), 'text/plain')
942 eq(subsubpart['message-id'],
943 '<002001c144a6$8752e060$56104586@oxy.edu>')
Barry Warsaw41075852001-09-23 03:18:13 +0000944
Barry Warsaw1f0fa922001-10-19 04:08:59 +0000945 def test_epilogue(self):
946 fp = openfile('msg_21.txt')
947 try:
948 text = fp.read()
949 finally:
950 fp.close()
951 msg = Message()
952 msg['From'] = 'aperson@dom.ain'
953 msg['To'] = 'bperson@dom.ain'
954 msg['Subject'] = 'Test'
955 msg.preamble = 'MIME message\n'
956 msg.epilogue = 'End of MIME message\n'
957 msg1 = MIMEText('One')
958 msg2 = MIMEText('Two')
959 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
960 msg.add_payload(msg1)
961 msg.add_payload(msg2)
962 sfp = StringIO()
963 g = Generator(sfp)
964 g(msg)
965 self.assertEqual(sfp.getvalue(), text)
966
Barry Warsaw41075852001-09-23 03:18:13 +0000967
Barry Warsaw08a534d2001-10-04 17:58:50 +0000968
969# A general test of parser->model->generator idempotency. IOW, read a message
970# in, parse it into a message object tree, then without touching the tree,
971# regenerate the plain text. The original text and the transformed text
972# should be identical. Note: that we ignore the Unix-From since that may
973# contain a changed date.
Barry Warsaw41075852001-09-23 03:18:13 +0000974class TestIdempotent(unittest.TestCase):
975 def _msgobj(self, filename):
976 fp = openfile(filename)
977 try:
978 data = fp.read()
979 finally:
980 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000981 msg = email.message_from_string(data)
982 return msg, data
Barry Warsaw41075852001-09-23 03:18:13 +0000983
984 def _idempotent(self, msg, text):
985 eq = self.assertEquals
986 s = StringIO()
987 g = Generator(s, maxheaderlen=0)
988 g(msg)
989 eq(text, s.getvalue())
990
991 def test_parse_text_message(self):
992 eq = self.assertEquals
993 msg, text = self._msgobj('msg_01.txt')
994 eq(msg.get_type(), 'text/plain')
995 eq(msg.get_main_type(), 'text')
996 eq(msg.get_subtype(), 'plain')
Barry Warsaw65279d02001-09-26 05:47:08 +0000997 eq(msg.get_params()[1], ('charset', 'us-ascii'))
Barry Warsaw41075852001-09-23 03:18:13 +0000998 eq(msg.get_param('charset'), 'us-ascii')
999 eq(msg.preamble, None)
1000 eq(msg.epilogue, None)
1001 self._idempotent(msg, text)
1002
1003 def test_parse_untyped_message(self):
1004 eq = self.assertEquals
1005 msg, text = self._msgobj('msg_03.txt')
1006 eq(msg.get_type(), None)
1007 eq(msg.get_params(), None)
1008 eq(msg.get_param('charset'), None)
1009 self._idempotent(msg, text)
1010
1011 def test_simple_multipart(self):
1012 msg, text = self._msgobj('msg_04.txt')
1013 self._idempotent(msg, text)
1014
1015 def test_MIME_digest(self):
1016 msg, text = self._msgobj('msg_02.txt')
1017 self._idempotent(msg, text)
1018
1019 def test_mixed_with_image(self):
1020 msg, text = self._msgobj('msg_06.txt')
1021 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +00001022
Barry Warsaw41075852001-09-23 03:18:13 +00001023 def test_multipart_report(self):
1024 msg, text = self._msgobj('msg_05.txt')
1025 self._idempotent(msg, text)
Barry Warsaw65279d02001-09-26 05:47:08 +00001026
1027 def test_dsn(self):
1028 msg, text = self._msgobj('msg_16.txt')
1029 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +00001030
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001031 def test_preamble_epilogue(self):
1032 msg, text = self._msgobj('msg_21.txt')
1033 self._idempotent(msg, text)
1034
Barry Warsaw763af412002-01-27 06:48:47 +00001035 def test_multipart_one_part(self):
1036 msg, text = self._msgobj('msg_23.txt')
1037 self._idempotent(msg, text)
1038
Barry Warsaw409a4c02002-04-10 21:01:31 +00001039 def test_multipart_no_parts(self):
1040 msg, text = self._msgobj('msg_24.txt')
1041 self._idempotent(msg, text)
1042
Barry Warsaw41075852001-09-23 03:18:13 +00001043 def test_content_type(self):
1044 eq = self.assertEquals
1045 # Get a message object and reset the seek pointer for other tests
1046 msg, text = self._msgobj('msg_05.txt')
1047 eq(msg.get_type(), 'multipart/report')
1048 # Test the Content-Type: parameters
1049 params = {}
Barry Warsaw65279d02001-09-26 05:47:08 +00001050 for pk, pv in msg.get_params():
Barry Warsaw41075852001-09-23 03:18:13 +00001051 params[pk] = pv
1052 eq(params['report-type'], 'delivery-status')
Barry Warsaw65279d02001-09-26 05:47:08 +00001053 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
Barry Warsaw41075852001-09-23 03:18:13 +00001054 eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
1055 eq(msg.epilogue, '\n\n')
1056 eq(len(msg.get_payload()), 3)
1057 # Make sure the subparts are what we expect
1058 msg1 = msg.get_payload(0)
1059 eq(msg1.get_type(), 'text/plain')
1060 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
1061 msg2 = msg.get_payload(1)
1062 eq(msg2.get_type(), None)
1063 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
1064 msg3 = msg.get_payload(2)
1065 eq(msg3.get_type(), 'message/rfc822')
1066 self.failUnless(isinstance(msg3, Message))
1067 msg4 = msg3.get_payload()
1068 self.failUnless(isinstance(msg4, Message))
1069 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
1070
1071 def test_parser(self):
1072 eq = self.assertEquals
1073 msg, text = self._msgobj('msg_06.txt')
1074 # Check some of the outer headers
1075 eq(msg.get_type(), 'message/rfc822')
1076 # Make sure there's exactly one thing in the payload and that's a
1077 # sub-Message object of type text/plain
1078 msg1 = msg.get_payload()
1079 self.failUnless(isinstance(msg1, Message))
1080 eq(msg1.get_type(), 'text/plain')
1081 self.failUnless(isinstance(msg1.get_payload(), StringType))
1082 eq(msg1.get_payload(), '\n')
Barry Warsaw41075852001-09-23 03:18:13 +00001083
Barry Warsaw08a534d2001-10-04 17:58:50 +00001084
1085# Test various other bits of the package's functionality
Barry Warsaw41075852001-09-23 03:18:13 +00001086class TestMiscellaneous(unittest.TestCase):
1087 def test_message_from_string(self):
1088 fp = openfile('msg_01.txt')
1089 try:
1090 text = fp.read()
1091 finally:
1092 fp.close()
1093 msg = email.message_from_string(text)
1094 s = StringIO()
1095 # Don't wrap/continue long headers since we're trying to test
1096 # idempotency.
1097 g = Generator(s, maxheaderlen=0)
1098 g(msg)
1099 self.assertEqual(text, s.getvalue())
1100
1101 def test_message_from_file(self):
1102 fp = openfile('msg_01.txt')
1103 try:
1104 text = fp.read()
1105 fp.seek(0)
1106 msg = email.message_from_file(fp)
1107 s = StringIO()
1108 # Don't wrap/continue long headers since we're trying to test
1109 # idempotency.
1110 g = Generator(s, maxheaderlen=0)
1111 g(msg)
1112 self.assertEqual(text, s.getvalue())
1113 finally:
1114 fp.close()
1115
1116 def test_message_from_string_with_class(self):
1117 unless = self.failUnless
1118 fp = openfile('msg_01.txt')
1119 try:
1120 text = fp.read()
1121 finally:
1122 fp.close()
1123 # Create a subclass
1124 class MyMessage(Message):
1125 pass
Tim Peters527e64f2001-10-04 05:36:56 +00001126
Barry Warsaw41075852001-09-23 03:18:13 +00001127 msg = email.message_from_string(text, MyMessage)
1128 unless(isinstance(msg, MyMessage))
1129 # Try something more complicated
1130 fp = openfile('msg_02.txt')
1131 try:
1132 text = fp.read()
1133 finally:
1134 fp.close()
1135 msg = email.message_from_string(text, MyMessage)
1136 for subpart in msg.walk():
1137 unless(isinstance(subpart, MyMessage))
1138
Barry Warsaw41075852001-09-23 03:18:13 +00001139 def test_message_from_file_with_class(self):
1140 unless = self.failUnless
1141 # Create a subclass
1142 class MyMessage(Message):
1143 pass
Tim Peters527e64f2001-10-04 05:36:56 +00001144
Barry Warsaw41075852001-09-23 03:18:13 +00001145 fp = openfile('msg_01.txt')
1146 try:
1147 msg = email.message_from_file(fp, MyMessage)
1148 finally:
1149 fp.close()
1150 unless(isinstance(msg, MyMessage))
1151 # Try something more complicated
1152 fp = openfile('msg_02.txt')
1153 try:
1154 msg = email.message_from_file(fp, MyMessage)
1155 finally:
1156 fp.close()
1157 for subpart in msg.walk():
1158 unless(isinstance(subpart, MyMessage))
1159
Barry Warsawfee435a2001-10-09 19:23:57 +00001160 def test__all__(self):
1161 module = __import__('email')
1162 all = module.__all__
1163 all.sort()
Barry Warsaw409a4c02002-04-10 21:01:31 +00001164 self.assertEqual(all, ['Charset', 'Encoders', 'Errors', 'Generator',
1165 'Header', 'Iterators', 'MIMEAudio',
1166 'MIMEBase', 'MIMEImage', 'MIMEMessage',
1167 'MIMEText', 'Message', 'Parser',
1168 'Utils', 'base64MIME',
1169 'message_from_file', 'message_from_string',
1170 'quopriMIME'])
Barry Warsawfee435a2001-10-09 19:23:57 +00001171
Barry Warsaw75edc6a2001-11-09 17:46:17 +00001172 def test_formatdate(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001173 now = time.time()
1174 self.assertEqual(Utils.parsedate(Utils.formatdate(now))[:6],
1175 time.gmtime(now)[:6])
Barry Warsaw75edc6a2001-11-09 17:46:17 +00001176
Barry Warsaw4586d2c2001-11-19 18:38:42 +00001177 def test_formatdate_localtime(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001178 now = time.time()
1179 self.assertEqual(
1180 Utils.parsedate(Utils.formatdate(now, localtime=1))[:6],
1181 time.localtime(now)[:6])
Barry Warsaw75a40fc2001-11-19 16:31:06 +00001182
1183 def test_parsedate_none(self):
Barry Warsaw19c10ca2001-11-13 18:01:37 +00001184 self.assertEqual(Utils.parsedate(''), None)
1185
Barry Warsaweae36ac2001-12-20 16:37:27 +00001186 def test_parseaddr_empty(self):
1187 self.assertEqual(Utils.parseaddr('<>'), ('', ''))
Barry Warsaw409a4c02002-04-10 21:01:31 +00001188 self.assertEqual(Utils.formataddr(Utils.parseaddr('<>')), '')
1189
1190 def test_noquote_dump(self):
1191 self.assertEqual(
1192 Utils.formataddr(('A Silly Person', 'person@dom.ain')),
1193 'A Silly Person <person@dom.ain>')
1194
1195 def test_escape_dump(self):
1196 self.assertEqual(
1197 Utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
1198 r'"A \(Very\) Silly Person" <person@dom.ain>')
1199 a = r'A \(Special\) Person'
1200 b = 'person@dom.ain'
1201 self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
1202
1203 def test_quote_dump(self):
1204 self.assertEqual(
1205 Utils.formataddr(('A Silly; Person', 'person@dom.ain')),
1206 r'"A Silly; Person" <person@dom.ain>')
1207
1208 def test_fix_eols(self):
1209 eq = self.assertEqual
1210 eq(Utils.fix_eols('hello'), 'hello')
1211 eq(Utils.fix_eols('hello\n'), 'hello\r\n')
1212 eq(Utils.fix_eols('hello\r'), 'hello\r\n')
1213 eq(Utils.fix_eols('hello\r\n'), 'hello\r\n')
1214 eq(Utils.fix_eols('hello\n\r'), 'hello\r\n\r\n')
1215
1216 def test_charset_richcomparisons(self):
1217 eq = self.assertEqual
1218 ne = self.failIfEqual
1219 cset1 = Charset()
1220 cset2 = Charset()
1221 eq(cset1, 'us-ascii')
1222 eq(cset1, 'US-ASCII')
1223 eq(cset1, 'Us-AsCiI')
1224 eq('us-ascii', cset1)
1225 eq('US-ASCII', cset1)
1226 eq('Us-AsCiI', cset1)
1227 ne(cset1, 'usascii')
1228 ne(cset1, 'USASCII')
1229 ne(cset1, 'UsAsCiI')
1230 ne('usascii', cset1)
1231 ne('USASCII', cset1)
1232 ne('UsAsCiI', cset1)
1233 eq(cset1, cset2)
1234 eq(cset2, cset1)
Barry Warsaweae36ac2001-12-20 16:37:27 +00001235
Barry Warsaw41075852001-09-23 03:18:13 +00001236
Barry Warsaw08a534d2001-10-04 17:58:50 +00001237
1238# Test the iterator/generators
Barry Warsaw41075852001-09-23 03:18:13 +00001239class TestIterators(TestEmailBase):
1240 def test_body_line_iterator(self):
1241 eq = self.assertEqual
1242 # First a simple non-multipart message
1243 msg = self._msgobj('msg_01.txt')
1244 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +00001245 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +00001246 eq(len(lines), 6)
1247 eq(EMPTYSTRING.join(lines), msg.get_payload())
1248 # Now a more complicated multipart
1249 msg = self._msgobj('msg_02.txt')
1250 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +00001251 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +00001252 eq(len(lines), 43)
Barry Warsaw08a534d2001-10-04 17:58:50 +00001253 eq(EMPTYSTRING.join(lines), openfile('msg_19.txt').read())
Barry Warsaw41075852001-09-23 03:18:13 +00001254
1255 def test_typed_subpart_iterator(self):
1256 eq = self.assertEqual
1257 msg = self._msgobj('msg_04.txt')
1258 it = Iterators.typed_subpart_iterator(msg, 'text')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001259 lines = []
1260 subparts = 0
1261 for subpart in it:
1262 subparts += 1
1263 lines.append(subpart.get_payload())
1264 eq(subparts, 2)
Barry Warsaw41075852001-09-23 03:18:13 +00001265 eq(EMPTYSTRING.join(lines), """\
1266a simple kind of mirror
1267to reflect upon our own
1268a simple kind of mirror
1269to reflect upon our own
1270""")
1271
Barry Warsawcdc632c2001-10-15 04:39:02 +00001272 def test_typed_subpart_iterator_default_type(self):
1273 eq = self.assertEqual
1274 msg = self._msgobj('msg_03.txt')
1275 it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
1276 lines = []
1277 subparts = 0
1278 for subpart in it:
1279 subparts += 1
1280 lines.append(subpart.get_payload())
1281 eq(subparts, 1)
1282 eq(EMPTYSTRING.join(lines), """\
1283
1284Hi,
1285
1286Do you like this message?
1287
1288-Me
1289""")
Barry Warsaw41075852001-09-23 03:18:13 +00001290
Barry Warsaw409a4c02002-04-10 21:01:31 +00001291
Barry Warsaw08a534d2001-10-04 17:58:50 +00001292
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001293class TestParsers(unittest.TestCase):
1294 def test_header_parser(self):
1295 eq = self.assertEqual
1296 # Parse only the headers of a complex multipart MIME document
1297 p = HeaderParser()
1298 fp = openfile('msg_02.txt')
1299 msg = p.parse(fp)
1300 eq(msg['from'], 'ppp-request@zzz.org')
1301 eq(msg['to'], 'ppp@zzz.org')
1302 eq(msg.get_type(), 'multipart/mixed')
1303 eq(msg.is_multipart(), 0)
1304 self.failUnless(isinstance(msg.get_payload(), StringType))
1305
Barry Warsaw409a4c02002-04-10 21:01:31 +00001306 def test_whitespace_continuaton(self):
1307 eq = self.assertEqual
1308 # This message contains a line after the Subject: header that has only
1309 # whitespace, but it is not empty!
1310 msg = email.message_from_string("""\
1311From: aperson@dom.ain
1312To: bperson@dom.ain
1313Subject: the next line has a space on it
1314
1315Date: Mon, 8 Apr 2002 15:09:19 -0400
1316Message-ID: spam
1317
1318Here's the message body
1319""")
1320 eq(msg['subject'], 'the next line has a space on it\n ')
1321 eq(msg['message-id'], 'spam')
1322 eq(msg.get_payload(), "Here's the message body\n")
1323
1324
1325
1326class TestBase64(unittest.TestCase):
1327 def test_len(self):
1328 eq = self.assertEqual
1329 eq(base64MIME.base64_len('hello'),
1330 len(base64MIME.encode('hello', eol='')))
1331 for size in range(15):
1332 if size == 0 : bsize = 0
1333 elif size <= 3 : bsize = 4
1334 elif size <= 6 : bsize = 8
1335 elif size <= 9 : bsize = 12
1336 elif size <= 12: bsize = 16
1337 else : bsize = 20
1338 eq(base64MIME.base64_len('x'*size), bsize)
1339
1340 def test_decode(self):
1341 eq = self.assertEqual
1342 eq(base64MIME.decode(''), '')
1343 eq(base64MIME.decode('aGVsbG8='), 'hello')
1344 eq(base64MIME.decode('aGVsbG8=', 'X'), 'hello')
1345 eq(base64MIME.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
1346
1347 def test_encode(self):
1348 eq = self.assertEqual
1349 eq(base64MIME.encode(''), '')
1350 eq(base64MIME.encode('hello'), 'aGVsbG8=\n')
1351 # Test the binary flag
1352 eq(base64MIME.encode('hello\n'), 'aGVsbG8K\n')
1353 eq(base64MIME.encode('hello\n', 0), 'aGVsbG8NCg==\n')
1354 # Test the maxlinelen arg
1355 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40), """\
1356eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1357eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1358eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1359eHh4eCB4eHh4IA==
1360""")
1361 # Test the eol argument
1362 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1363eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1364eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1365eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1366eHh4eCB4eHh4IA==\r
1367""")
1368
1369 def test_header_encode(self):
1370 eq = self.assertEqual
1371 he = base64MIME.header_encode
1372 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
1373 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
1374 # Test the charset option
1375 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
1376 # Test the keep_eols flag
1377 eq(he('hello\nworld', keep_eols=1),
1378 '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
1379 # Test the maxlinelen argument
1380 eq(he('xxxx ' * 20, maxlinelen=40), """\
1381=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
1382 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
1383 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
1384 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
1385 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
1386 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1387 # Test the eol argument
1388 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1389=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
1390 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
1391 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
1392 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
1393 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
1394 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1395
1396
1397
1398class TestQuopri(unittest.TestCase):
1399 def setUp(self):
1400 self.hlit = [chr(x) for x in range(ord('a'), ord('z')+1)] + \
1401 [chr(x) for x in range(ord('A'), ord('Z')+1)] + \
1402 [chr(x) for x in range(ord('0'), ord('9')+1)] + \
1403 ['!', '*', '+', '-', '/', ' ']
1404 self.hnon = [chr(x) for x in range(256) if chr(x) not in self.hlit]
1405 assert len(self.hlit) + len(self.hnon) == 256
1406 self.blit = [chr(x) for x in range(ord(' '), ord('~')+1)] + ['\t']
1407 self.blit.remove('=')
1408 self.bnon = [chr(x) for x in range(256) if chr(x) not in self.blit]
1409 assert len(self.blit) + len(self.bnon) == 256
1410
1411 def test_header_quopri_check(self):
1412 for c in self.hlit:
1413 self.failIf(quopriMIME.header_quopri_check(c))
1414 for c in self.hnon:
1415 self.failUnless(quopriMIME.header_quopri_check(c))
1416
1417 def test_body_quopri_check(self):
1418 for c in self.blit:
1419 self.failIf(quopriMIME.body_quopri_check(c))
1420 for c in self.bnon:
1421 self.failUnless(quopriMIME.body_quopri_check(c))
1422
1423 def test_header_quopri_len(self):
1424 eq = self.assertEqual
1425 hql = quopriMIME.header_quopri_len
1426 enc = quopriMIME.header_encode
1427 for s in ('hello', 'h@e@l@l@o@'):
1428 # Empty charset and no line-endings. 7 == RFC chrome
1429 eq(hql(s), len(enc(s, charset='', eol=''))-7)
1430 for c in self.hlit:
1431 eq(hql(c), 1)
1432 for c in self.hnon:
1433 eq(hql(c), 3)
1434
1435 def test_body_quopri_len(self):
1436 eq = self.assertEqual
1437 bql = quopriMIME.body_quopri_len
1438 for c in self.blit:
1439 eq(bql(c), 1)
1440 for c in self.bnon:
1441 eq(bql(c), 3)
1442
1443 def test_quote_unquote_idempotent(self):
1444 for x in range(256):
1445 c = chr(x)
1446 self.assertEqual(quopriMIME.unquote(quopriMIME.quote(c)), c)
1447
1448 def test_header_encode(self):
1449 eq = self.assertEqual
1450 he = quopriMIME.header_encode
1451 eq(he('hello'), '=?iso-8859-1?q?hello?=')
1452 eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
1453 # Test the charset option
1454 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
1455 # Test the keep_eols flag
1456 eq(he('hello\nworld', keep_eols=1), '=?iso-8859-1?q?hello=0Aworld?=')
1457 # Test a non-ASCII character
1458 eq(he('helloÇthere'), '=?iso-8859-1?q?hello=C7there?=')
1459 # Test the maxlinelen argument
1460 eq(he('xxxx ' * 20, maxlinelen=40), """\
1461=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
1462 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
1463 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
1464 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
1465 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
1466 # Test the eol argument
1467 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1468=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
1469 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
1470 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
1471 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
1472 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
1473
1474 def test_decode(self):
1475 eq = self.assertEqual
1476 eq(quopriMIME.decode(''), '')
1477 eq(quopriMIME.decode('hello'), 'hello')
1478 eq(quopriMIME.decode('hello', 'X'), 'hello')
1479 eq(quopriMIME.decode('hello\nworld', 'X'), 'helloXworld')
1480
1481 def test_encode(self):
1482 eq = self.assertEqual
1483 eq(quopriMIME.encode(''), '')
1484 eq(quopriMIME.encode('hello'), 'hello')
1485 # Test the binary flag
1486 eq(quopriMIME.encode('hello\r\nworld'), 'hello\nworld')
1487 eq(quopriMIME.encode('hello\r\nworld', 0), 'hello\nworld')
1488 # Test the maxlinelen arg
1489 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40), """\
1490xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
1491 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
1492x xxxx xxxx xxxx xxxx=20""")
1493 # Test the eol argument
1494 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1495xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
1496 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
1497x xxxx xxxx xxxx xxxx=20""")
1498 eq(quopriMIME.encode("""\
1499one line
1500
1501two line"""), """\
1502one line
1503
1504two line""")
1505
1506
1507
1508# Test the Charset class
1509class TestCharset(unittest.TestCase):
1510 def test_idempotent(self):
1511 eq = self.assertEqual
1512 # Make sure us-ascii = no Unicode conversion
1513 c = Charset('us-ascii')
1514 s = 'Hello World!'
1515 sp = c.to_splittable(s)
1516 eq(s, c.from_splittable(sp))
1517 # test 8-bit idempotency with us-ascii
1518 s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
1519 sp = c.to_splittable(s)
1520 eq(s, c.from_splittable(sp))
1521
1522
1523
1524# Test multilingual MIME headers.
1525class TestHeader(unittest.TestCase):
1526 def test_simple(self):
1527 eq = self.assertEqual
1528 h = Header('Hello World!')
1529 eq(h.encode(), 'Hello World!')
1530 h.append('Goodbye World!')
1531 eq(h.encode(), 'Hello World! Goodbye World!')
1532
1533 def test_header_needs_no_decoding(self):
1534 h = 'no decoding needed'
1535 self.assertEqual(decode_header(h), [(h, None)])
1536
1537 def test_long(self):
1538 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.",
1539 maxlinelen=76)
1540 for l in h.encode().split('\n '):
1541 self.failUnless(len(l) <= 76)
1542
1543 def test_multilingual(self):
1544 eq = self.assertEqual
1545 g = Charset("iso-8859-1")
1546 cz = Charset("iso-8859-2")
1547 utf8 = Charset("utf-8")
1548 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. "
1549 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
1550 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")
1551 h = Header(g_head, g)
1552 h.append(cz_head, cz)
1553 h.append(utf8_head, utf8)
1554 enc = h.encode()
1555 eq(enc, """=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
1556 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
1557 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
1558 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
1559 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
1560 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
1561 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
1562 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
1563 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
1564 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
1565 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
1566 =?utf-8?b?cyBOdW5zdHVjayBnaXQgdW5k?=
1567 =?utf-8?b?IFNsb3Rlcm1leWVyPyBKYSEgQmVpaGVyaHVuZCBkYXMgT2Rl?=
1568 =?utf-8?b?ciBkaWUgRmxpcHBlcndhbGR0?=
1569 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""")
1570 eq(decode_header(enc),
1571 [(g_head, "iso-8859-1"), (cz_head, "iso-8859-2"),
1572 (utf8_head, "utf-8")])
1573
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001574
1575
Barry Warsaw41075852001-09-23 03:18:13 +00001576def suite():
1577 suite = unittest.TestSuite()
1578 suite.addTest(unittest.makeSuite(TestMessageAPI))
1579 suite.addTest(unittest.makeSuite(TestEncoders))
1580 suite.addTest(unittest.makeSuite(TestLongHeaders))
1581 suite.addTest(unittest.makeSuite(TestFromMangling))
Barry Warsawfee435a2001-10-09 19:23:57 +00001582 suite.addTest(unittest.makeSuite(TestMIMEAudio))
Barry Warsaw65279d02001-09-26 05:47:08 +00001583 suite.addTest(unittest.makeSuite(TestMIMEImage))
1584 suite.addTest(unittest.makeSuite(TestMIMEText))
Barry Warsaw41075852001-09-23 03:18:13 +00001585 suite.addTest(unittest.makeSuite(TestMultipartMixed))
1586 suite.addTest(unittest.makeSuite(TestNonConformant))
1587 suite.addTest(unittest.makeSuite(TestRFC2047))
Barry Warsaw65279d02001-09-26 05:47:08 +00001588 suite.addTest(unittest.makeSuite(TestMIMEMessage))
Barry Warsaw41075852001-09-23 03:18:13 +00001589 suite.addTest(unittest.makeSuite(TestIdempotent))
1590 suite.addTest(unittest.makeSuite(TestMiscellaneous))
1591 suite.addTest(unittest.makeSuite(TestIterators))
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001592 suite.addTest(unittest.makeSuite(TestParsers))
Barry Warsaw409a4c02002-04-10 21:01:31 +00001593 suite.addTest(unittest.makeSuite(TestBase64))
1594 suite.addTest(unittest.makeSuite(TestQuopri))
1595 suite.addTest(unittest.makeSuite(TestHeader))
1596 suite.addTest(unittest.makeSuite(TestCharset))
Barry Warsaw41075852001-09-23 03:18:13 +00001597 return suite
1598
1599
Barry Warsaw08a534d2001-10-04 17:58:50 +00001600
Guido van Rossum78f0dd32001-12-07 21:07:08 +00001601if __name__ == '__main__':
Barry Warsaw409a4c02002-04-10 21:01:31 +00001602 unittest.main(defaultTest='suite')