blob: 1647f4494735d9c73be6b19168ed85b7638abab9 [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
Barry Warsawb6a92132002-06-28 23:49:33 +00009import difflib
Barry Warsaw41075852001-09-23 03:18:13 +000010from cStringIO import StringIO
Barry Warsaw2c685062002-06-02 19:09:27 +000011from types import StringType, ListType
Barry Warsaw409a4c02002-04-10 21:01:31 +000012import warnings
Barry Warsaw41075852001-09-23 03:18:13 +000013
14import email
15
Barry Warsaw409a4c02002-04-10 21:01:31 +000016from email.Charset import Charset
Barry Warsawc53b29e2002-07-09 16:36:36 +000017from email.Header import Header, decode_header, make_header
Barry Warsawbf7a59d2001-10-11 15:44:50 +000018from email.Parser import Parser, HeaderParser
Barry Warsaw41075852001-09-23 03:18:13 +000019from email.Generator import Generator, DecodedGenerator
20from email.Message import Message
Barry Warsawfee435a2001-10-09 19:23:57 +000021from email.MIMEAudio import MIMEAudio
Barry Warsaw65279d02001-09-26 05:47:08 +000022from email.MIMEText import MIMEText
23from email.MIMEImage import MIMEImage
Barry Warsaw41075852001-09-23 03:18:13 +000024from email.MIMEBase import MIMEBase
Barry Warsaw65279d02001-09-26 05:47:08 +000025from email.MIMEMessage import MIMEMessage
Barry Warsaw329d3af2002-07-09 02:38:24 +000026from email.MIMEMultipart import MIMEMultipart
Barry Warsaw41075852001-09-23 03:18:13 +000027from email import Utils
28from email import Errors
29from email import Encoders
30from email import Iterators
Barry Warsaw409a4c02002-04-10 21:01:31 +000031from email import base64MIME
32from email import quopriMIME
Barry Warsaw41075852001-09-23 03:18:13 +000033
Barry Warsawc9ad32c2002-04-15 22:14:06 +000034import test_support
Guido van Rossum78f0dd32001-12-07 21:07:08 +000035from test_support import findfile, __file__ as test_support_file
Barry Warsaw41075852001-09-23 03:18:13 +000036
Barry Warsawc9ad32c2002-04-15 22:14:06 +000037
Barry Warsaw41075852001-09-23 03:18:13 +000038NL = '\n'
39EMPTYSTRING = ''
Barry Warsaw07227d12001-10-17 20:52:26 +000040SPACE = ' '
Barry Warsaw41075852001-09-23 03:18:13 +000041
Barry Warsaw409a4c02002-04-10 21:01:31 +000042# We don't care about DeprecationWarnings
43warnings.filterwarnings('ignore', '', DeprecationWarning, __name__)
44
Barry Warsaw41075852001-09-23 03:18:13 +000045
Barry Warsaw08a534d2001-10-04 17:58:50 +000046
Barry Warsaw41075852001-09-23 03:18:13 +000047def openfile(filename):
Guido van Rossum78f0dd32001-12-07 21:07:08 +000048 path = os.path.join(os.path.dirname(test_support_file), 'data', filename)
Barry Warsaw41075852001-09-23 03:18:13 +000049 return open(path)
50
51
Barry Warsaw08a534d2001-10-04 17:58:50 +000052
Barry Warsaw41075852001-09-23 03:18:13 +000053# Base test class
54class TestEmailBase(unittest.TestCase):
Barry Warsaw329d3af2002-07-09 02:38:24 +000055 if hasattr(difflib, 'ndiff'):
56 # Python 2.2 and beyond
57 def ndiffAssertEqual(self, first, second):
58 """Like failUnlessEqual except use ndiff for readable output."""
59 if first <> second:
Barry Warsawc53b29e2002-07-09 16:36:36 +000060 sfirst = str(first)
61 ssecond = str(second)
62 diff = difflib.ndiff(sfirst.splitlines(), ssecond.splitlines())
Barry Warsaw329d3af2002-07-09 02:38:24 +000063 fp = StringIO()
64 print >> fp, NL, NL.join(diff)
65 raise self.failureException, fp.getvalue()
66 else:
67 # Python 2.1
68 ndiffAssertEqual = unittest.TestCase.assertEqual
Barry Warsawb6a92132002-06-28 23:49:33 +000069
Barry Warsaw41075852001-09-23 03:18:13 +000070 def _msgobj(self, filename):
Barry Warsaw409a4c02002-04-10 21:01:31 +000071 fp = openfile(findfile(filename))
Barry Warsaw41075852001-09-23 03:18:13 +000072 try:
Barry Warsaw65279d02001-09-26 05:47:08 +000073 msg = email.message_from_file(fp)
Barry Warsaw41075852001-09-23 03:18:13 +000074 finally:
75 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +000076 return msg
Barry Warsaw41075852001-09-23 03:18:13 +000077
78
Barry Warsaw08a534d2001-10-04 17:58:50 +000079
Barry Warsaw41075852001-09-23 03:18:13 +000080# Test various aspects of the Message class's API
81class TestMessageAPI(TestEmailBase):
Barry Warsaw2f6a0b02001-10-09 15:49:35 +000082 def test_get_all(self):
83 eq = self.assertEqual
84 msg = self._msgobj('msg_20.txt')
85 eq(msg.get_all('cc'), ['ccc@zzz.org', 'ddd@zzz.org', 'eee@zzz.org'])
86 eq(msg.get_all('xx', 'n/a'), 'n/a')
87
Barry Warsaw409a4c02002-04-10 21:01:31 +000088 def test_getset_charset(self):
89 eq = self.assertEqual
90 msg = Message()
91 eq(msg.get_charset(), None)
92 charset = Charset('iso-8859-1')
93 msg.set_charset(charset)
94 eq(msg['mime-version'], '1.0')
95 eq(msg.get_type(), 'text/plain')
96 eq(msg['content-type'], 'text/plain; charset="iso-8859-1"')
97 eq(msg.get_param('charset'), 'iso-8859-1')
98 eq(msg['content-transfer-encoding'], 'quoted-printable')
99 eq(msg.get_charset().input_charset, 'iso-8859-1')
100 # Remove the charset
101 msg.set_charset(None)
102 eq(msg.get_charset(), None)
103 eq(msg['content-type'], 'text/plain')
104 # Try adding a charset when there's already MIME headers present
105 msg = Message()
106 msg['MIME-Version'] = '2.0'
107 msg['Content-Type'] = 'text/x-weird'
108 msg['Content-Transfer-Encoding'] = 'quinted-puntable'
109 msg.set_charset(charset)
110 eq(msg['mime-version'], '2.0')
111 eq(msg['content-type'], 'text/x-weird; charset="iso-8859-1"')
112 eq(msg['content-transfer-encoding'], 'quinted-puntable')
113
114 def test_set_charset_from_string(self):
115 eq = self.assertEqual
116 msg = Message()
117 msg.set_charset('us-ascii')
118 eq(msg.get_charset().input_charset, 'us-ascii')
119 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
120
121 def test_set_payload_with_charset(self):
122 msg = Message()
123 charset = Charset('iso-8859-1')
124 msg.set_payload('This is a string payload', charset)
125 self.assertEqual(msg.get_charset().input_charset, 'iso-8859-1')
126
Barry Warsaw41075852001-09-23 03:18:13 +0000127 def test_get_charsets(self):
128 eq = self.assertEqual
Tim Peters527e64f2001-10-04 05:36:56 +0000129
Barry Warsaw65279d02001-09-26 05:47:08 +0000130 msg = self._msgobj('msg_08.txt')
131 charsets = msg.get_charsets()
132 eq(charsets, [None, 'us-ascii', 'iso-8859-1', 'iso-8859-2', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000133
Barry Warsaw65279d02001-09-26 05:47:08 +0000134 msg = self._msgobj('msg_09.txt')
135 charsets = msg.get_charsets('dingbat')
136 eq(charsets, ['dingbat', 'us-ascii', 'iso-8859-1', 'dingbat',
137 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000138
Barry Warsaw65279d02001-09-26 05:47:08 +0000139 msg = self._msgobj('msg_12.txt')
140 charsets = msg.get_charsets()
141 eq(charsets, [None, 'us-ascii', 'iso-8859-1', None, 'iso-8859-2',
142 'iso-8859-3', 'us-ascii', 'koi8-r'])
Barry Warsaw41075852001-09-23 03:18:13 +0000143
144 def test_get_filename(self):
145 eq = self.assertEqual
146
Barry Warsaw65279d02001-09-26 05:47:08 +0000147 msg = self._msgobj('msg_04.txt')
148 filenames = [p.get_filename() for p in msg.get_payload()]
Barry Warsaw41075852001-09-23 03:18:13 +0000149 eq(filenames, ['msg.txt', 'msg.txt'])
150
Barry Warsaw65279d02001-09-26 05:47:08 +0000151 msg = self._msgobj('msg_07.txt')
152 subpart = msg.get_payload(1)
Barry Warsaw41075852001-09-23 03:18:13 +0000153 eq(subpart.get_filename(), 'dingusfish.gif')
154
155 def test_get_boundary(self):
156 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000157 msg = self._msgobj('msg_07.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000158 # No quotes!
Barry Warsaw65279d02001-09-26 05:47:08 +0000159 eq(msg.get_boundary(), 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000160
161 def test_set_boundary(self):
162 eq = self.assertEqual
163 # This one has no existing boundary parameter, but the Content-Type:
164 # header appears fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000165 msg = self._msgobj('msg_01.txt')
166 msg.set_boundary('BOUNDARY')
167 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000168 eq(header.lower(), 'content-type')
Barry Warsaw9546e792002-06-29 05:58:45 +0000169 eq(value, 'text/plain; charset="us-ascii"; boundary="BOUNDARY"')
Barry Warsaw41075852001-09-23 03:18:13 +0000170 # This one has a Content-Type: header, with a boundary, stuck in the
171 # middle of its headers. Make sure the order is preserved; it should
172 # be fifth.
Barry Warsaw65279d02001-09-26 05:47:08 +0000173 msg = self._msgobj('msg_04.txt')
174 msg.set_boundary('BOUNDARY')
175 header, value = msg.items()[4]
Barry Warsaw41075852001-09-23 03:18:13 +0000176 eq(header.lower(), 'content-type')
177 eq(value, 'multipart/mixed; boundary="BOUNDARY"')
178 # And this one has no Content-Type: header at all.
Barry Warsaw65279d02001-09-26 05:47:08 +0000179 msg = self._msgobj('msg_03.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000180 self.assertRaises(Errors.HeaderParseError,
Barry Warsaw65279d02001-09-26 05:47:08 +0000181 msg.set_boundary, 'BOUNDARY')
Barry Warsaw41075852001-09-23 03:18:13 +0000182
183 def test_get_decoded_payload(self):
184 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000185 msg = self._msgobj('msg_10.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000186 # The outer message is a multipart
Barry Warsaw65279d02001-09-26 05:47:08 +0000187 eq(msg.get_payload(decode=1), None)
Barry Warsaw41075852001-09-23 03:18:13 +0000188 # Subpart 1 is 7bit encoded
Barry Warsaw65279d02001-09-26 05:47:08 +0000189 eq(msg.get_payload(0).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000190 'This is a 7bit encoded message.\n')
191 # Subpart 2 is quopri
Barry Warsaw65279d02001-09-26 05:47:08 +0000192 eq(msg.get_payload(1).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000193 '\xa1This is a Quoted Printable encoded message!\n')
194 # Subpart 3 is base64
Barry Warsaw65279d02001-09-26 05:47:08 +0000195 eq(msg.get_payload(2).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000196 'This is a Base64 encoded message.')
197 # Subpart 4 has no Content-Transfer-Encoding: header.
Barry Warsaw65279d02001-09-26 05:47:08 +0000198 eq(msg.get_payload(3).get_payload(decode=1),
Barry Warsaw41075852001-09-23 03:18:13 +0000199 'This has no Content-Transfer-Encoding: header.\n')
200
201 def test_decoded_generator(self):
202 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000203 msg = self._msgobj('msg_07.txt')
204 fp = openfile('msg_17.txt')
205 try:
206 text = fp.read()
207 finally:
208 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +0000209 s = StringIO()
210 g = DecodedGenerator(s)
Barry Warsaw2c685062002-06-02 19:09:27 +0000211 g.flatten(msg)
Barry Warsaw65279d02001-09-26 05:47:08 +0000212 eq(s.getvalue(), text)
Barry Warsaw41075852001-09-23 03:18:13 +0000213
214 def test__contains__(self):
215 msg = Message()
216 msg['From'] = 'Me'
217 msg['to'] = 'You'
218 # Check for case insensitivity
219 self.failUnless('from' in msg)
220 self.failUnless('From' in msg)
221 self.failUnless('FROM' in msg)
222 self.failUnless('to' in msg)
223 self.failUnless('To' in msg)
224 self.failUnless('TO' in msg)
225
226 def test_as_string(self):
227 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000228 msg = self._msgobj('msg_01.txt')
Barry Warsaw41075852001-09-23 03:18:13 +0000229 fp = openfile('msg_01.txt')
230 try:
231 text = fp.read()
232 finally:
233 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000234 eq(text, msg.as_string())
235 fullrepr = str(msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000236 lines = fullrepr.split('\n')
237 self.failUnless(lines[0].startswith('From '))
238 eq(text, NL.join(lines[1:]))
239
240 def test_bad_param(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000241 msg = email.message_from_string("Content-Type: blarg; baz; boo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000242 self.assertEqual(msg.get_param('baz'), '')
243
244 def test_missing_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000245 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000246 self.assertEqual(msg.get_filename(), None)
247
248 def test_bogus_filename(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000249 msg = email.message_from_string(
250 "Content-Disposition: blarg; filename\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000251 self.assertEqual(msg.get_filename(), '')
Tim Peters527e64f2001-10-04 05:36:56 +0000252
Barry Warsaw41075852001-09-23 03:18:13 +0000253 def test_missing_boundary(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000254 msg = email.message_from_string("From: foo\n")
Barry Warsaw41075852001-09-23 03:18:13 +0000255 self.assertEqual(msg.get_boundary(), None)
256
Barry Warsaw65279d02001-09-26 05:47:08 +0000257 def test_get_params(self):
258 eq = self.assertEqual
259 msg = email.message_from_string(
260 'X-Header: foo=one; bar=two; baz=three\n')
261 eq(msg.get_params(header='x-header'),
262 [('foo', 'one'), ('bar', 'two'), ('baz', 'three')])
263 msg = email.message_from_string(
264 'X-Header: foo; bar=one; baz=two\n')
265 eq(msg.get_params(header='x-header'),
266 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
267 eq(msg.get_params(), None)
268 msg = email.message_from_string(
269 'X-Header: foo; bar="one"; baz=two\n')
270 eq(msg.get_params(header='x-header'),
271 [('foo', ''), ('bar', 'one'), ('baz', 'two')])
272
Barry Warsaw409a4c02002-04-10 21:01:31 +0000273 def test_get_param_liberal(self):
274 msg = Message()
275 msg['Content-Type'] = 'Content-Type: Multipart/mixed; boundary = "CPIMSSMTPC06p5f3tG"'
276 self.assertEqual(msg.get_param('boundary'), 'CPIMSSMTPC06p5f3tG')
277
Barry Warsaw65279d02001-09-26 05:47:08 +0000278 def test_get_param(self):
279 eq = self.assertEqual
280 msg = email.message_from_string(
281 "X-Header: foo=one; bar=two; baz=three\n")
282 eq(msg.get_param('bar', header='x-header'), 'two')
283 eq(msg.get_param('quuz', header='x-header'), None)
284 eq(msg.get_param('quuz'), None)
285 msg = email.message_from_string(
286 'X-Header: foo; bar="one"; baz=two\n')
287 eq(msg.get_param('foo', header='x-header'), '')
288 eq(msg.get_param('bar', header='x-header'), 'one')
289 eq(msg.get_param('baz', header='x-header'), 'two')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000290 # XXX: We are not RFC-2045 compliant! We cannot parse:
291 # msg["Content-Type"] = 'text/plain; weird="hey; dolly? [you] @ <\\"home\\">?"'
292 # msg.get_param("weird")
293 # yet.
Barry Warsaw65279d02001-09-26 05:47:08 +0000294
Barry Warsaw2539cf52001-10-25 22:43:46 +0000295 def test_get_param_funky_continuation_lines(self):
296 msg = self._msgobj('msg_22.txt')
297 self.assertEqual(msg.get_payload(1).get_param('name'), 'wibble.JPG')
298
Barry Warsaw65279d02001-09-26 05:47:08 +0000299 def test_has_key(self):
300 msg = email.message_from_string('Header: exists')
301 self.failUnless(msg.has_key('header'))
302 self.failUnless(msg.has_key('Header'))
303 self.failUnless(msg.has_key('HEADER'))
304 self.failIf(msg.has_key('headeri'))
305
Barry Warsaw409a4c02002-04-10 21:01:31 +0000306 def test_set_param(self):
307 eq = self.assertEqual
308 msg = Message()
309 msg.set_param('charset', 'iso-2022-jp')
310 eq(msg.get_param('charset'), 'iso-2022-jp')
311 msg.set_param('importance', 'high value')
312 eq(msg.get_param('importance'), 'high value')
313 eq(msg.get_param('importance', unquote=0), '"high value"')
314 eq(msg.get_params(), [('text/plain', ''),
315 ('charset', 'iso-2022-jp'),
316 ('importance', 'high value')])
317 eq(msg.get_params(unquote=0), [('text/plain', ''),
318 ('charset', '"iso-2022-jp"'),
319 ('importance', '"high value"')])
320 msg.set_param('charset', 'iso-9999-xx', header='X-Jimmy')
321 eq(msg.get_param('charset', header='X-Jimmy'), 'iso-9999-xx')
Barry Warsaw41075852001-09-23 03:18:13 +0000322
Barry Warsaw409a4c02002-04-10 21:01:31 +0000323 def test_del_param(self):
324 eq = self.assertEqual
325 msg = self._msgobj('msg_05.txt')
326 eq(msg.get_params(),
327 [('multipart/report', ''), ('report-type', 'delivery-status'),
328 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
329 old_val = msg.get_param("report-type")
330 msg.del_param("report-type")
331 eq(msg.get_params(),
332 [('multipart/report', ''),
Barry Warsaw16f90552002-04-16 05:06:42 +0000333 ('boundary', 'D1690A7AC1.996856090/mail.example.com')])
Barry Warsaw409a4c02002-04-10 21:01:31 +0000334 msg.set_param("report-type", old_val)
335 eq(msg.get_params(),
336 [('multipart/report', ''),
337 ('boundary', 'D1690A7AC1.996856090/mail.example.com'),
338 ('report-type', old_val)])
339
340 def test_set_type(self):
341 eq = self.assertEqual
342 msg = Message()
343 self.assertRaises(ValueError, msg.set_type, 'text')
344 msg.set_type('text/plain')
345 eq(msg['content-type'], 'text/plain')
346 msg.set_param('charset', 'us-ascii')
347 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
348 msg.set_type('text/html')
349 eq(msg['content-type'], 'text/html; charset="us-ascii"')
350
Barry Warsaw16f90552002-04-16 05:06:42 +0000351
Barry Warsaw08a534d2001-10-04 17:58:50 +0000352
Barry Warsaw41075852001-09-23 03:18:13 +0000353# Test the email.Encoders module
354class TestEncoders(unittest.TestCase):
355 def test_encode_noop(self):
356 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000357 msg = MIMEText('hello world', _encoder=Encoders.encode_noop)
Barry Warsaw41075852001-09-23 03:18:13 +0000358 eq(msg.get_payload(), 'hello world\n')
Barry Warsaw41075852001-09-23 03:18:13 +0000359
360 def test_encode_7bit(self):
361 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000362 msg = MIMEText('hello world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000363 eq(msg.get_payload(), 'hello world\n')
364 eq(msg['content-transfer-encoding'], '7bit')
Barry Warsaw65279d02001-09-26 05:47:08 +0000365 msg = MIMEText('hello \x7f world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000366 eq(msg.get_payload(), 'hello \x7f world\n')
367 eq(msg['content-transfer-encoding'], '7bit')
368
369 def test_encode_8bit(self):
370 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000371 msg = MIMEText('hello \x80 world', _encoder=Encoders.encode_7or8bit)
Barry Warsaw41075852001-09-23 03:18:13 +0000372 eq(msg.get_payload(), 'hello \x80 world\n')
373 eq(msg['content-transfer-encoding'], '8bit')
374
Barry Warsaw409a4c02002-04-10 21:01:31 +0000375 def test_encode_empty_payload(self):
376 eq = self.assertEqual
377 msg = Message()
378 msg.set_charset('us-ascii')
379 eq(msg['content-transfer-encoding'], '7bit')
380
Barry Warsaw41075852001-09-23 03:18:13 +0000381 def test_encode_base64(self):
382 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000383 msg = MIMEText('hello world', _encoder=Encoders.encode_base64)
Barry Warsaw41075852001-09-23 03:18:13 +0000384 eq(msg.get_payload(), 'aGVsbG8gd29ybGQK\n')
385 eq(msg['content-transfer-encoding'], 'base64')
386
387 def test_encode_quoted_printable(self):
388 eq = self.assertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000389 msg = MIMEText('hello world', _encoder=Encoders.encode_quopri)
Barry Warsaw41075852001-09-23 03:18:13 +0000390 eq(msg.get_payload(), 'hello=20world\n')
391 eq(msg['content-transfer-encoding'], 'quoted-printable')
392
Barry Warsaw409a4c02002-04-10 21:01:31 +0000393 def test_default_cte(self):
394 eq = self.assertEqual
395 msg = MIMEText('hello world')
396 eq(msg['content-transfer-encoding'], '7bit')
397
398 def test_default_cte(self):
399 eq = self.assertEqual
400 # With no explicit _charset its us-ascii, and all are 7-bit
401 msg = MIMEText('hello world')
402 eq(msg['content-transfer-encoding'], '7bit')
403 # Similar, but with 8-bit data
404 msg = MIMEText('hello \xf8 world')
405 eq(msg['content-transfer-encoding'], '8bit')
406 # And now with a different charset
407 msg = MIMEText('hello \xf8 world', _charset='iso-8859-1')
408 eq(msg['content-transfer-encoding'], 'quoted-printable')
409
Barry Warsaw41075852001-09-23 03:18:13 +0000410
Barry Warsaw08a534d2001-10-04 17:58:50 +0000411
412# Test long header wrapping
Barry Warsawb6a92132002-06-28 23:49:33 +0000413class TestLongHeaders(TestEmailBase):
414 def test_split_long_continuation(self):
415 eq = self.ndiffAssertEqual
416 msg = email.message_from_string("""\
417Subject: bug demonstration
418\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
419\tmore text
420
421test
422""")
423 sfp = StringIO()
424 g = Generator(sfp)
425 g.flatten(msg)
426 eq(sfp.getvalue(), """\
427Subject: bug demonstration
428\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
429\tmore text
430
431test
432""")
433
434 def test_another_long_almost_unsplittable_header(self):
435 eq = self.ndiffAssertEqual
436 hstr = """\
437bug demonstration
438\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
439\tmore text"""
440 h = Header(hstr, continuation_ws='\t')
441 eq(h.encode(), """\
442bug demonstration
443\t12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
444\tmore text""")
445 h = Header(hstr)
446 eq(h.encode(), """\
447bug demonstration
448 12345678911234567892123456789312345678941234567895123456789612345678971234567898112345678911234567892123456789112345678911234567892123456789
449 more text""")
450
451 def test_long_nonstring(self):
452 eq = self.ndiffAssertEqual
453 g = Charset("iso-8859-1")
454 cz = Charset("iso-8859-2")
455 utf8 = Charset("utf-8")
456 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. "
457 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
458 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")
459 h = Header(g_head, g)
460 h.append(cz_head, cz)
461 h.append(utf8_head, utf8)
462 msg = Message()
463 msg['Subject'] = h
464 sfp = StringIO()
465 g = Generator(sfp)
466 g.flatten(msg)
467 eq(sfp.getvalue(), '''\
468Subject: =?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
469 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
470 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
471 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
472 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
473 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
474 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
475 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
476 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
477 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
478 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
479 =?utf-8?b?cyBOdW5zdHVjayBnaXQgdW5k?=
480 =?utf-8?b?IFNsb3Rlcm1leWVyPyBKYSEgQmVpaGVyaHVuZCBkYXMgT2Rl?=
481 =?utf-8?b?ciBkaWUgRmxpcHBlcndhbGR0?=
482 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=
483
484''')
485 eq(h.encode(), '''\
486=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
487 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
488 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
489 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
490 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
491 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
492 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
493 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
494 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
495 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
496 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
497 =?utf-8?b?cyBOdW5zdHVjayBnaXQgdW5k?=
498 =?utf-8?b?IFNsb3Rlcm1leWVyPyBKYSEgQmVpaGVyaHVuZCBkYXMgT2Rl?=
499 =?utf-8?b?ciBkaWUgRmxpcHBlcndhbGR0?=
500 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=''')
501
502 def test_long_header_encode(self):
503 eq = self.ndiffAssertEqual
504 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
505 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
506 header_name='X-Foobar-Spoink-Defrobnit')
507 eq(h.encode(), '''\
508wasnipoop; giraffes="very-long-necked-animals";
509 spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
510
511 def test_long_header_encode_with_tab_continuation(self):
512 eq = self.ndiffAssertEqual
513 h = Header('wasnipoop; giraffes="very-long-necked-animals"; '
514 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"',
515 header_name='X-Foobar-Spoink-Defrobnit',
516 continuation_ws='\t')
517 eq(h.encode(), '''\
518wasnipoop; giraffes="very-long-necked-animals";
519\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
520
Barry Warsaw41075852001-09-23 03:18:13 +0000521 def test_header_splitter(self):
Barry Warsawb6a92132002-06-28 23:49:33 +0000522 eq = self.ndiffAssertEqual
Barry Warsaw65279d02001-09-26 05:47:08 +0000523 msg = MIMEText('')
Barry Warsaw41075852001-09-23 03:18:13 +0000524 # It'd be great if we could use add_header() here, but that doesn't
525 # guarantee an order of the parameters.
526 msg['X-Foobar-Spoink-Defrobnit'] = (
527 'wasnipoop; giraffes="very-long-necked-animals"; '
528 'spooge="yummy"; hippos="gargantuan"; marshmallows="gooey"')
529 sfp = StringIO()
530 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +0000531 g.flatten(msg)
Barry Warsawb6a92132002-06-28 23:49:33 +0000532 eq(sfp.getvalue(), '''\
Barry Warsaw409a4c02002-04-10 21:01:31 +0000533Content-Type: text/plain; charset="us-ascii"
534MIME-Version: 1.0
535Content-Transfer-Encoding: 7bit
536X-Foobar-Spoink-Defrobnit: wasnipoop; giraffes="very-long-necked-animals";
Barry Warsaw16f90552002-04-16 05:06:42 +0000537\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"
Barry Warsaw409a4c02002-04-10 21:01:31 +0000538
539''')
Barry Warsaw41075852001-09-23 03:18:13 +0000540
Barry Warsaw07227d12001-10-17 20:52:26 +0000541 def test_no_semis_header_splitter(self):
Barry Warsawb6a92132002-06-28 23:49:33 +0000542 eq = self.ndiffAssertEqual
Barry Warsaw07227d12001-10-17 20:52:26 +0000543 msg = Message()
544 msg['From'] = 'test@dom.ain'
Barry Warsawb6a92132002-06-28 23:49:33 +0000545 msg['References'] = SPACE.join(['<%d@dom.ain>' % i for i in range(10)])
Barry Warsaw07227d12001-10-17 20:52:26 +0000546 msg.set_payload('Test')
547 sfp = StringIO()
548 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +0000549 g.flatten(msg)
Barry Warsawb6a92132002-06-28 23:49:33 +0000550 eq(sfp.getvalue(), """\
Barry Warsaw07227d12001-10-17 20:52:26 +0000551From: test@dom.ain
552References: <0@dom.ain> <1@dom.ain> <2@dom.ain> <3@dom.ain> <4@dom.ain>
Tim Peterse0c446b2001-10-18 21:57:37 +0000553\t<5@dom.ain> <6@dom.ain> <7@dom.ain> <8@dom.ain> <9@dom.ain>
Barry Warsaw07227d12001-10-17 20:52:26 +0000554
555Test""")
556
557 def test_no_split_long_header(self):
Barry Warsawb6a92132002-06-28 23:49:33 +0000558 eq = self.ndiffAssertEqual
559 hstr = 'References: ' + 'x' * 80
560 h = Header(hstr, continuation_ws='\t')
561 eq(h.encode(), """\
562References: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx""")
Barry Warsaw07227d12001-10-17 20:52:26 +0000563
Barry Warsaw409a4c02002-04-10 21:01:31 +0000564 def test_splitting_multiple_long_lines(self):
Barry Warsawb6a92132002-06-28 23:49:33 +0000565 eq = self.ndiffAssertEqual
566 hstr = """\
Barry Warsaw409a4c02002-04-10 21:01:31 +0000567from 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 +0000568\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)
569\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 +0000570"""
Barry Warsawb6a92132002-06-28 23:49:33 +0000571 h = Header(hstr, continuation_ws='\t')
572 eq(h.encode(), """\
573from babylon.socal-raves.org (localhost [127.0.0.1]);
Barry Warsaw16f90552002-04-16 05:06:42 +0000574\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
575\tfor <mailman-admin@babylon.socal-raves.org>;
576\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
577\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
578\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
579\tfor <mailman-admin@babylon.socal-raves.org>;
580\tSat, 2 Feb 2002 17:00:06 -0800 (PST)
581\tfrom babylon.socal-raves.org (localhost [127.0.0.1]);
582\tby babylon.socal-raves.org (Postfix) with ESMTP id B570E51B81;
583\tfor <mailman-admin@babylon.socal-raves.org>;
Barry Warsawb6a92132002-06-28 23:49:33 +0000584\tSat, 2 Feb 2002 17:00:06 -0800 (PST)""")
Barry Warsaw409a4c02002-04-10 21:01:31 +0000585
Barry Warsaw41075852001-09-23 03:18:13 +0000586
Barry Warsaw08a534d2001-10-04 17:58:50 +0000587
588# Test mangling of "From " lines in the body of a message
Barry Warsaw41075852001-09-23 03:18:13 +0000589class TestFromMangling(unittest.TestCase):
590 def setUp(self):
591 self.msg = Message()
592 self.msg['From'] = 'aaa@bbb.org'
Barry Warsaw2c685062002-06-02 19:09:27 +0000593 self.msg.set_payload("""\
Barry Warsaw41075852001-09-23 03:18:13 +0000594From the desk of A.A.A.:
595Blah blah blah
596""")
597
598 def test_mangled_from(self):
599 s = StringIO()
600 g = Generator(s, mangle_from_=1)
Barry Warsaw2c685062002-06-02 19:09:27 +0000601 g.flatten(self.msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000602 self.assertEqual(s.getvalue(), """\
603From: aaa@bbb.org
604
605>From the desk of A.A.A.:
606Blah blah blah
607""")
608
609 def test_dont_mangle_from(self):
610 s = StringIO()
611 g = Generator(s, mangle_from_=0)
Barry Warsaw2c685062002-06-02 19:09:27 +0000612 g.flatten(self.msg)
Barry Warsaw41075852001-09-23 03:18:13 +0000613 self.assertEqual(s.getvalue(), """\
614From: aaa@bbb.org
615
616From the desk of A.A.A.:
617Blah blah blah
618""")
619
620
Barry Warsaw08a534d2001-10-04 17:58:50 +0000621
Barry Warsawfee435a2001-10-09 19:23:57 +0000622# Test the basic MIMEAudio class
623class TestMIMEAudio(unittest.TestCase):
624 def setUp(self):
625 # In Python, audiotest.au lives in Lib/test not Lib/test/data
626 fp = open(findfile('audiotest.au'))
627 try:
628 self._audiodata = fp.read()
629 finally:
630 fp.close()
631 self._au = MIMEAudio(self._audiodata)
632
633 def test_guess_minor_type(self):
634 self.assertEqual(self._au.get_type(), 'audio/basic')
635
636 def test_encoding(self):
637 payload = self._au.get_payload()
638 self.assertEqual(base64.decodestring(payload), self._audiodata)
639
640 def checkSetMinor(self):
641 au = MIMEAudio(self._audiodata, 'fish')
642 self.assertEqual(im.get_type(), 'audio/fish')
643
644 def test_custom_encoder(self):
645 eq = self.assertEqual
646 def encoder(msg):
647 orig = msg.get_payload()
648 msg.set_payload(0)
649 msg['Content-Transfer-Encoding'] = 'broken64'
650 au = MIMEAudio(self._audiodata, _encoder=encoder)
651 eq(au.get_payload(), 0)
652 eq(au['content-transfer-encoding'], 'broken64')
653
654 def test_add_header(self):
655 eq = self.assertEqual
656 unless = self.failUnless
657 self._au.add_header('Content-Disposition', 'attachment',
658 filename='audiotest.au')
659 eq(self._au['content-disposition'],
660 'attachment; filename="audiotest.au"')
661 eq(self._au.get_params(header='content-disposition'),
662 [('attachment', ''), ('filename', 'audiotest.au')])
663 eq(self._au.get_param('filename', header='content-disposition'),
664 'audiotest.au')
665 missing = []
666 eq(self._au.get_param('attachment', header='content-disposition'), '')
667 unless(self._au.get_param('foo', failobj=missing,
668 header='content-disposition') is missing)
669 # Try some missing stuff
670 unless(self._au.get_param('foobar', missing) is missing)
671 unless(self._au.get_param('attachment', missing,
672 header='foobar') is missing)
673
674
675
Barry Warsaw65279d02001-09-26 05:47:08 +0000676# Test the basic MIMEImage class
677class TestMIMEImage(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000678 def setUp(self):
679 fp = openfile('PyBanner048.gif')
680 try:
681 self._imgdata = fp.read()
682 finally:
683 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +0000684 self._im = MIMEImage(self._imgdata)
Barry Warsaw41075852001-09-23 03:18:13 +0000685
686 def test_guess_minor_type(self):
687 self.assertEqual(self._im.get_type(), 'image/gif')
688
689 def test_encoding(self):
690 payload = self._im.get_payload()
691 self.assertEqual(base64.decodestring(payload), self._imgdata)
692
693 def checkSetMinor(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000694 im = MIMEImage(self._imgdata, 'fish')
Barry Warsaw41075852001-09-23 03:18:13 +0000695 self.assertEqual(im.get_type(), 'image/fish')
696
697 def test_custom_encoder(self):
698 eq = self.assertEqual
699 def encoder(msg):
700 orig = msg.get_payload()
701 msg.set_payload(0)
702 msg['Content-Transfer-Encoding'] = 'broken64'
Barry Warsaw65279d02001-09-26 05:47:08 +0000703 im = MIMEImage(self._imgdata, _encoder=encoder)
Barry Warsaw41075852001-09-23 03:18:13 +0000704 eq(im.get_payload(), 0)
705 eq(im['content-transfer-encoding'], 'broken64')
706
707 def test_add_header(self):
708 eq = self.assertEqual
709 unless = self.failUnless
710 self._im.add_header('Content-Disposition', 'attachment',
711 filename='dingusfish.gif')
712 eq(self._im['content-disposition'],
713 'attachment; filename="dingusfish.gif"')
714 eq(self._im.get_params(header='content-disposition'),
Barry Warsaw65279d02001-09-26 05:47:08 +0000715 [('attachment', ''), ('filename', 'dingusfish.gif')])
Barry Warsaw41075852001-09-23 03:18:13 +0000716 eq(self._im.get_param('filename', header='content-disposition'),
717 'dingusfish.gif')
718 missing = []
Barry Warsaw65279d02001-09-26 05:47:08 +0000719 eq(self._im.get_param('attachment', header='content-disposition'), '')
720 unless(self._im.get_param('foo', failobj=missing,
Barry Warsaw41075852001-09-23 03:18:13 +0000721 header='content-disposition') is missing)
722 # Try some missing stuff
723 unless(self._im.get_param('foobar', missing) is missing)
724 unless(self._im.get_param('attachment', missing,
725 header='foobar') is missing)
726
727
Barry Warsaw08a534d2001-10-04 17:58:50 +0000728
Barry Warsaw65279d02001-09-26 05:47:08 +0000729# Test the basic MIMEText class
730class TestMIMEText(unittest.TestCase):
Barry Warsaw41075852001-09-23 03:18:13 +0000731 def setUp(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000732 self._msg = MIMEText('hello there')
Barry Warsaw41075852001-09-23 03:18:13 +0000733
734 def test_types(self):
735 eq = self.assertEqual
736 unless = self.failUnless
737 eq(self._msg.get_type(), 'text/plain')
738 eq(self._msg.get_param('charset'), 'us-ascii')
739 missing = []
740 unless(self._msg.get_param('foobar', missing) is missing)
741 unless(self._msg.get_param('charset', missing, header='foobar')
742 is missing)
743
744 def test_payload(self):
745 self.assertEqual(self._msg.get_payload(), 'hello there\n')
746 self.failUnless(not self._msg.is_multipart())
747
Barry Warsaw409a4c02002-04-10 21:01:31 +0000748 def test_charset(self):
749 eq = self.assertEqual
750 msg = MIMEText('hello there', _charset='us-ascii')
751 eq(msg.get_charset().input_charset, 'us-ascii')
752 eq(msg['content-type'], 'text/plain; charset="us-ascii"')
753
Barry Warsaw41075852001-09-23 03:18:13 +0000754
Barry Warsaw08a534d2001-10-04 17:58:50 +0000755
756# Test a more complicated multipart/mixed type message
Barry Warsaw41075852001-09-23 03:18:13 +0000757class TestMultipartMixed(unittest.TestCase):
758 def setUp(self):
759 fp = openfile('PyBanner048.gif')
760 try:
761 data = fp.read()
762 finally:
763 fp.close()
764
765 container = MIMEBase('multipart', 'mixed', boundary='BOUNDARY')
Barry Warsaw65279d02001-09-26 05:47:08 +0000766 image = MIMEImage(data, name='dingusfish.gif')
Barry Warsaw41075852001-09-23 03:18:13 +0000767 image.add_header('content-disposition', 'attachment',
768 filename='dingusfish.gif')
Barry Warsaw65279d02001-09-26 05:47:08 +0000769 intro = MIMEText('''\
Barry Warsaw41075852001-09-23 03:18:13 +0000770Hi there,
771
772This is the dingus fish.
773''')
Barry Warsaw2c685062002-06-02 19:09:27 +0000774 container.attach(intro)
775 container.attach(image)
Barry Warsaw41075852001-09-23 03:18:13 +0000776 container['From'] = 'Barry <barry@digicool.com>'
777 container['To'] = 'Dingus Lovers <cravindogs@cravindogs.com>'
778 container['Subject'] = 'Here is your dingus fish'
Tim Peters527e64f2001-10-04 05:36:56 +0000779
Barry Warsaw41075852001-09-23 03:18:13 +0000780 now = 987809702.54848599
781 timetuple = time.localtime(now)
782 if timetuple[-1] == 0:
783 tzsecs = time.timezone
784 else:
785 tzsecs = time.altzone
786 if tzsecs > 0:
787 sign = '-'
788 else:
789 sign = '+'
790 tzoffset = ' %s%04d' % (sign, tzsecs / 36)
791 container['Date'] = time.strftime(
792 '%a, %d %b %Y %H:%M:%S',
793 time.localtime(now)) + tzoffset
794 self._msg = container
795 self._im = image
796 self._txt = intro
797
798 def test_hierarchy(self):
799 # convenience
800 eq = self.assertEqual
801 unless = self.failUnless
802 raises = self.assertRaises
803 # tests
804 m = self._msg
805 unless(m.is_multipart())
806 eq(m.get_type(), 'multipart/mixed')
807 eq(len(m.get_payload()), 2)
808 raises(IndexError, m.get_payload, 2)
809 m0 = m.get_payload(0)
810 m1 = m.get_payload(1)
811 unless(m0 is self._txt)
812 unless(m1 is self._im)
813 eq(m.get_payload(), [m0, m1])
814 unless(not m0.is_multipart())
815 unless(not m1.is_multipart())
816
Barry Warsaw409a4c02002-04-10 21:01:31 +0000817 def test_no_parts_in_a_multipart(self):
818 outer = MIMEBase('multipart', 'mixed')
819 outer['Subject'] = 'A subject'
820 outer['To'] = 'aperson@dom.ain'
821 outer['From'] = 'bperson@dom.ain'
822 outer.preamble = ''
823 outer.epilogue = ''
824 outer.set_boundary('BOUNDARY')
825 msg = MIMEText('hello world')
826 self.assertEqual(outer.as_string(), '''\
827Content-Type: multipart/mixed; boundary="BOUNDARY"
828MIME-Version: 1.0
829Subject: A subject
830To: aperson@dom.ain
831From: bperson@dom.ain
832
833--BOUNDARY
834
835
836--BOUNDARY--
Barry Warsaw16f90552002-04-16 05:06:42 +0000837''')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000838
839 def test_one_part_in_a_multipart(self):
840 outer = MIMEBase('multipart', 'mixed')
841 outer['Subject'] = 'A subject'
842 outer['To'] = 'aperson@dom.ain'
843 outer['From'] = 'bperson@dom.ain'
844 outer.preamble = ''
845 outer.epilogue = ''
846 outer.set_boundary('BOUNDARY')
847 msg = MIMEText('hello world')
848 outer.attach(msg)
849 self.assertEqual(outer.as_string(), '''\
850Content-Type: multipart/mixed; boundary="BOUNDARY"
851MIME-Version: 1.0
852Subject: A subject
853To: aperson@dom.ain
854From: bperson@dom.ain
855
856--BOUNDARY
857Content-Type: text/plain; charset="us-ascii"
858MIME-Version: 1.0
859Content-Transfer-Encoding: 7bit
860
861hello world
862
863--BOUNDARY--
Barry Warsaw16f90552002-04-16 05:06:42 +0000864''')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000865
866 def test_seq_parts_in_a_multipart(self):
867 outer = MIMEBase('multipart', 'mixed')
868 outer['Subject'] = 'A subject'
869 outer['To'] = 'aperson@dom.ain'
870 outer['From'] = 'bperson@dom.ain'
871 outer.preamble = ''
872 outer.epilogue = ''
873 msg = MIMEText('hello world')
874 outer.attach(msg)
875 outer.set_boundary('BOUNDARY')
876 self.assertEqual(outer.as_string(), '''\
877Content-Type: multipart/mixed; boundary="BOUNDARY"
878MIME-Version: 1.0
879Subject: A subject
880To: aperson@dom.ain
881From: bperson@dom.ain
882
883--BOUNDARY
884Content-Type: text/plain; charset="us-ascii"
885MIME-Version: 1.0
886Content-Transfer-Encoding: 7bit
887
888hello world
889
890--BOUNDARY--
Barry Warsaw16f90552002-04-16 05:06:42 +0000891''')
Barry Warsaw409a4c02002-04-10 21:01:31 +0000892
Barry Warsaw41075852001-09-23 03:18:13 +0000893
Barry Warsaw08a534d2001-10-04 17:58:50 +0000894
895# Test some badly formatted messages
Barry Warsaw41075852001-09-23 03:18:13 +0000896class TestNonConformant(TestEmailBase):
897 def test_parse_missing_minor_type(self):
898 eq = self.assertEqual
899 msg = self._msgobj('msg_14.txt')
900 eq(msg.get_type(), 'text')
901 eq(msg.get_main_type(), 'text')
902 self.failUnless(msg.get_subtype() is None)
903
904 def test_bogus_boundary(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +0000905 fp = openfile(findfile('msg_15.txt'))
Barry Warsaw41075852001-09-23 03:18:13 +0000906 try:
907 data = fp.read()
908 finally:
909 fp.close()
910 p = Parser()
911 # Note, under a future non-strict parsing mode, this would parse the
912 # message into the intended message tree.
913 self.assertRaises(Errors.BoundaryError, p.parsestr, data)
914
Barry Warsaw409a4c02002-04-10 21:01:31 +0000915 def test_multipart_no_boundary(self):
916 fp = openfile(findfile('msg_25.txt'))
Barry Warsaw329d3af2002-07-09 02:38:24 +0000917 try:
918 self.assertRaises(Errors.BoundaryError,
919 email.message_from_file, fp)
920 finally:
921 fp.close()
Barry Warsaw409a4c02002-04-10 21:01:31 +0000922
Barry Warsaw41075852001-09-23 03:18:13 +0000923
Barry Warsaw08a534d2001-10-04 17:58:50 +0000924
925# Test RFC 2047 header encoding and decoding
Barry Warsaw41075852001-09-23 03:18:13 +0000926class TestRFC2047(unittest.TestCase):
927 def test_iso_8859_1(self):
928 eq = self.assertEqual
929 s = '=?iso-8859-1?q?this=20is=20some=20text?='
930 eq(Utils.decode(s), 'this is some text')
931 s = '=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?='
Barry Warsaw409a4c02002-04-10 21:01:31 +0000932 eq(Utils.decode(s), u'Keld J\xf8rn Simonsen')
Barry Warsaw41075852001-09-23 03:18:13 +0000933 s = '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=' \
934 '=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?='
935 eq(Utils.decode(s), 'If you can read this you understand the example.')
936 s = '=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?='
937 eq(Utils.decode(s),
938 u'\u05dd\u05d5\u05dc\u05e9 \u05df\u05d1 \u05d9\u05dc\u05d8\u05e4\u05e0')
939 s = '=?iso-8859-1?q?this=20is?= =?iso-8859-1?q?some=20text?='
Barry Warsaw409a4c02002-04-10 21:01:31 +0000940 eq(Utils.decode(s), u'this issome text')
941 s = '=?iso-8859-1?q?this=20is_?= =?iso-8859-1?q?some=20text?='
Barry Warsaw41075852001-09-23 03:18:13 +0000942 eq(Utils.decode(s), u'this is some text')
943
944 def test_encode_header(self):
945 eq = self.assertEqual
946 s = 'this is some text'
947 eq(Utils.encode(s), '=?iso-8859-1?q?this=20is=20some=20text?=')
948 s = 'Keld_J\xf8rn_Simonsen'
949 eq(Utils.encode(s), '=?iso-8859-1?q?Keld_J=F8rn_Simonsen?=')
950 s1 = 'If you can read this yo'
951 s2 = 'u understand the example.'
952 eq(Utils.encode(s1, encoding='b'),
953 '=?iso-8859-1?b?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=')
954 eq(Utils.encode(s2, charset='iso-8859-2', encoding='b'),
955 '=?iso-8859-2?b?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=')
956
957
Barry Warsaw08a534d2001-10-04 17:58:50 +0000958
959# Test the MIMEMessage class
Barry Warsaw65279d02001-09-26 05:47:08 +0000960class TestMIMEMessage(TestEmailBase):
Barry Warsaw41075852001-09-23 03:18:13 +0000961 def setUp(self):
962 fp = openfile('msg_11.txt')
Barry Warsaw329d3af2002-07-09 02:38:24 +0000963 try:
964 self._text = fp.read()
965 finally:
966 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +0000967
968 def test_type_error(self):
Barry Warsaw65279d02001-09-26 05:47:08 +0000969 self.assertRaises(TypeError, MIMEMessage, 'a plain string')
Barry Warsaw41075852001-09-23 03:18:13 +0000970
971 def test_valid_argument(self):
972 eq = self.assertEqual
Barry Warsaw2c685062002-06-02 19:09:27 +0000973 unless = self.failUnless
Barry Warsaw41075852001-09-23 03:18:13 +0000974 subject = 'A sub-message'
975 m = Message()
976 m['Subject'] = subject
Barry Warsaw65279d02001-09-26 05:47:08 +0000977 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +0000978 eq(r.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +0000979 payload = r.get_payload()
980 unless(type(payload), ListType)
981 eq(len(payload), 1)
982 subpart = payload[0]
983 unless(subpart is m)
984 eq(subpart['subject'], subject)
985
986 def test_bad_multipart(self):
987 eq = self.assertEqual
988 msg1 = Message()
989 msg1['Subject'] = 'subpart 1'
990 msg2 = Message()
991 msg2['Subject'] = 'subpart 2'
992 r = MIMEMessage(msg1)
993 self.assertRaises(Errors.MultipartConversionError, r.attach, msg2)
Barry Warsaw41075852001-09-23 03:18:13 +0000994
995 def test_generate(self):
996 # First craft the message to be encapsulated
997 m = Message()
998 m['Subject'] = 'An enclosed message'
Barry Warsaw2c685062002-06-02 19:09:27 +0000999 m.set_payload('Here is the body of the message.\n')
Barry Warsaw65279d02001-09-26 05:47:08 +00001000 r = MIMEMessage(m)
Barry Warsaw41075852001-09-23 03:18:13 +00001001 r['Subject'] = 'The enclosing message'
1002 s = StringIO()
1003 g = Generator(s)
Barry Warsaw2c685062002-06-02 19:09:27 +00001004 g.flatten(r)
Barry Warsaw65279d02001-09-26 05:47:08 +00001005 self.assertEqual(s.getvalue(), """\
1006Content-Type: message/rfc822
1007MIME-Version: 1.0
1008Subject: The enclosing message
1009
1010Subject: An enclosed message
1011
1012Here is the body of the message.
1013""")
1014
1015 def test_parse_message_rfc822(self):
1016 eq = self.assertEqual
Barry Warsaw2c685062002-06-02 19:09:27 +00001017 unless = self.failUnless
Barry Warsaw65279d02001-09-26 05:47:08 +00001018 msg = self._msgobj('msg_11.txt')
1019 eq(msg.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +00001020 payload = msg.get_payload()
1021 unless(isinstance(payload, ListType))
1022 eq(len(payload), 1)
1023 submsg = payload[0]
Barry Warsaw65279d02001-09-26 05:47:08 +00001024 self.failUnless(isinstance(submsg, Message))
1025 eq(submsg['subject'], 'An enclosed message')
1026 eq(submsg.get_payload(), 'Here is the body of the message.\n')
1027
1028 def test_dsn(self):
1029 eq = self.assertEqual
1030 unless = self.failUnless
Barry Warsawc53b29e2002-07-09 16:36:36 +00001031 # msg 16 is a Delivery Status Notification, see RFC 1894
Barry Warsaw65279d02001-09-26 05:47:08 +00001032 msg = self._msgobj('msg_16.txt')
1033 eq(msg.get_type(), 'multipart/report')
1034 unless(msg.is_multipart())
1035 eq(len(msg.get_payload()), 3)
1036 # Subpart 1 is a text/plain, human readable section
1037 subpart = msg.get_payload(0)
1038 eq(subpart.get_type(), 'text/plain')
1039 eq(subpart.get_payload(), """\
1040This report relates to a message you sent with the following header fields:
1041
1042 Message-id: <002001c144a6$8752e060$56104586@oxy.edu>
1043 Date: Sun, 23 Sep 2001 20:10:55 -0700
1044 From: "Ian T. Henry" <henryi@oxy.edu>
1045 To: SoCal Raves <scr@socal-raves.org>
1046 Subject: [scr] yeah for Ians!!
1047
1048Your message cannot be delivered to the following recipients:
1049
1050 Recipient address: jangel1@cougar.noc.ucla.edu
1051 Reason: recipient reached disk quota
1052
1053""")
1054 # Subpart 2 contains the machine parsable DSN information. It
1055 # consists of two blocks of headers, represented by two nested Message
1056 # objects.
1057 subpart = msg.get_payload(1)
1058 eq(subpart.get_type(), 'message/delivery-status')
1059 eq(len(subpart.get_payload()), 2)
1060 # message/delivery-status should treat each block as a bunch of
1061 # headers, i.e. a bunch of Message objects.
1062 dsn1 = subpart.get_payload(0)
1063 unless(isinstance(dsn1, Message))
1064 eq(dsn1['original-envelope-id'], '0GK500B4HD0888@cougar.noc.ucla.edu')
1065 eq(dsn1.get_param('dns', header='reporting-mta'), '')
1066 # Try a missing one <wink>
1067 eq(dsn1.get_param('nsd', header='reporting-mta'), None)
1068 dsn2 = subpart.get_payload(1)
1069 unless(isinstance(dsn2, Message))
1070 eq(dsn2['action'], 'failed')
1071 eq(dsn2.get_params(header='original-recipient'),
1072 [('rfc822', ''), ('jangel1@cougar.noc.ucla.edu', '')])
1073 eq(dsn2.get_param('rfc822', header='final-recipient'), '')
1074 # Subpart 3 is the original message
1075 subpart = msg.get_payload(2)
1076 eq(subpart.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +00001077 payload = subpart.get_payload()
1078 unless(isinstance(payload, ListType))
1079 eq(len(payload), 1)
1080 subsubpart = payload[0]
Barry Warsaw65279d02001-09-26 05:47:08 +00001081 unless(isinstance(subsubpart, Message))
1082 eq(subsubpart.get_type(), 'text/plain')
1083 eq(subsubpart['message-id'],
1084 '<002001c144a6$8752e060$56104586@oxy.edu>')
Barry Warsaw41075852001-09-23 03:18:13 +00001085
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001086 def test_epilogue(self):
1087 fp = openfile('msg_21.txt')
1088 try:
1089 text = fp.read()
1090 finally:
1091 fp.close()
1092 msg = Message()
1093 msg['From'] = 'aperson@dom.ain'
1094 msg['To'] = 'bperson@dom.ain'
1095 msg['Subject'] = 'Test'
1096 msg.preamble = 'MIME message\n'
1097 msg.epilogue = 'End of MIME message\n'
1098 msg1 = MIMEText('One')
1099 msg2 = MIMEText('Two')
1100 msg.add_header('Content-Type', 'multipart/mixed', boundary='BOUNDARY')
Barry Warsaw2c685062002-06-02 19:09:27 +00001101 msg.attach(msg1)
1102 msg.attach(msg2)
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001103 sfp = StringIO()
1104 g = Generator(sfp)
Barry Warsaw2c685062002-06-02 19:09:27 +00001105 g.flatten(msg)
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001106 self.assertEqual(sfp.getvalue(), text)
1107
Barry Warsaw329d3af2002-07-09 02:38:24 +00001108 def test_default_type(self):
1109 eq = self.assertEqual
1110 fp = openfile('msg_30.txt')
1111 try:
1112 msg = email.message_from_file(fp)
1113 finally:
1114 fp.close()
1115 container1 = msg.get_payload(0)
1116 eq(container1.get_default_type(), 'message/rfc822')
1117 eq(container1.get_type(), None)
1118 container2 = msg.get_payload(1)
1119 eq(container2.get_default_type(), 'message/rfc822')
1120 eq(container2.get_type(), None)
1121 container1a = container1.get_payload(0)
1122 eq(container1a.get_default_type(), 'text/plain')
1123 eq(container1a.get_type(), 'text/plain')
1124 container2a = container2.get_payload(0)
1125 eq(container2a.get_default_type(), 'text/plain')
1126 eq(container2a.get_type(), 'text/plain')
1127
1128 def test_default_type_with_explicit_container_type(self):
1129 eq = self.assertEqual
1130 fp = openfile('msg_28.txt')
1131 try:
1132 msg = email.message_from_file(fp)
1133 finally:
1134 fp.close()
1135 container1 = msg.get_payload(0)
1136 eq(container1.get_default_type(), 'message/rfc822')
1137 eq(container1.get_type(), 'message/rfc822')
1138 container2 = msg.get_payload(1)
1139 eq(container2.get_default_type(), 'message/rfc822')
1140 eq(container2.get_type(), 'message/rfc822')
1141 container1a = container1.get_payload(0)
1142 eq(container1a.get_default_type(), 'text/plain')
1143 eq(container1a.get_type(), 'text/plain')
1144 container2a = container2.get_payload(0)
1145 eq(container2a.get_default_type(), 'text/plain')
1146 eq(container2a.get_type(), 'text/plain')
1147
1148 def test_default_type_non_parsed(self):
1149 eq = self.assertEqual
1150 neq = self.ndiffAssertEqual
1151 # Set up container
1152 container = MIMEMultipart('digest', 'BOUNDARY')
1153 container.epilogue = '\n'
1154 # Set up subparts
1155 subpart1a = MIMEText('message 1\n')
1156 subpart2a = MIMEText('message 2\n')
1157 subpart1 = MIMEMessage(subpart1a)
1158 subpart2 = MIMEMessage(subpart2a)
1159 container.attach(subpart1)
1160 container.attach(subpart2)
1161 eq(subpart1.get_type(), 'message/rfc822')
1162 eq(subpart1.get_default_type(), 'message/rfc822')
1163 eq(subpart2.get_type(), 'message/rfc822')
1164 eq(subpart2.get_default_type(), 'message/rfc822')
1165 neq(container.as_string(0), '''\
1166Content-Type: multipart/digest; boundary="BOUNDARY"
1167MIME-Version: 1.0
1168
1169--BOUNDARY
1170Content-Type: message/rfc822
1171MIME-Version: 1.0
1172
1173Content-Type: text/plain; charset="us-ascii"
1174MIME-Version: 1.0
1175Content-Transfer-Encoding: 7bit
1176
1177message 1
1178
1179--BOUNDARY
1180Content-Type: message/rfc822
1181MIME-Version: 1.0
1182
1183Content-Type: text/plain; charset="us-ascii"
1184MIME-Version: 1.0
1185Content-Transfer-Encoding: 7bit
1186
1187message 2
1188
1189--BOUNDARY--
1190''')
1191 del subpart1['content-type']
1192 del subpart1['mime-version']
1193 del subpart2['content-type']
1194 del subpart2['mime-version']
1195 eq(subpart1.get_type(), None)
1196 eq(subpart1.get_default_type(), 'message/rfc822')
1197 eq(subpart2.get_type(), None)
1198 eq(subpart2.get_default_type(), 'message/rfc822')
1199 neq(container.as_string(0), '''\
1200Content-Type: multipart/digest; boundary="BOUNDARY"
1201MIME-Version: 1.0
1202
1203--BOUNDARY
1204
1205Content-Type: text/plain; charset="us-ascii"
1206MIME-Version: 1.0
1207Content-Transfer-Encoding: 7bit
1208
1209message 1
1210
1211--BOUNDARY
1212
1213Content-Type: text/plain; charset="us-ascii"
1214MIME-Version: 1.0
1215Content-Transfer-Encoding: 7bit
1216
1217message 2
1218
1219--BOUNDARY--
1220''')
1221
Barry Warsaw41075852001-09-23 03:18:13 +00001222
Barry Warsaw08a534d2001-10-04 17:58:50 +00001223
1224# A general test of parser->model->generator idempotency. IOW, read a message
1225# in, parse it into a message object tree, then without touching the tree,
1226# regenerate the plain text. The original text and the transformed text
1227# should be identical. Note: that we ignore the Unix-From since that may
1228# contain a changed date.
Barry Warsawb6a92132002-06-28 23:49:33 +00001229class TestIdempotent(TestEmailBase):
Barry Warsaw41075852001-09-23 03:18:13 +00001230 def _msgobj(self, filename):
1231 fp = openfile(filename)
1232 try:
1233 data = fp.read()
1234 finally:
1235 fp.close()
Barry Warsaw65279d02001-09-26 05:47:08 +00001236 msg = email.message_from_string(data)
1237 return msg, data
Barry Warsaw41075852001-09-23 03:18:13 +00001238
1239 def _idempotent(self, msg, text):
Barry Warsawb6a92132002-06-28 23:49:33 +00001240 eq = self.ndiffAssertEqual
Barry Warsaw41075852001-09-23 03:18:13 +00001241 s = StringIO()
1242 g = Generator(s, maxheaderlen=0)
Barry Warsaw2c685062002-06-02 19:09:27 +00001243 g.flatten(msg)
Barry Warsaw41075852001-09-23 03:18:13 +00001244 eq(text, s.getvalue())
1245
1246 def test_parse_text_message(self):
1247 eq = self.assertEquals
1248 msg, text = self._msgobj('msg_01.txt')
1249 eq(msg.get_type(), 'text/plain')
1250 eq(msg.get_main_type(), 'text')
1251 eq(msg.get_subtype(), 'plain')
Barry Warsaw65279d02001-09-26 05:47:08 +00001252 eq(msg.get_params()[1], ('charset', 'us-ascii'))
Barry Warsaw41075852001-09-23 03:18:13 +00001253 eq(msg.get_param('charset'), 'us-ascii')
1254 eq(msg.preamble, None)
1255 eq(msg.epilogue, None)
1256 self._idempotent(msg, text)
1257
1258 def test_parse_untyped_message(self):
1259 eq = self.assertEquals
1260 msg, text = self._msgobj('msg_03.txt')
1261 eq(msg.get_type(), None)
1262 eq(msg.get_params(), None)
1263 eq(msg.get_param('charset'), None)
1264 self._idempotent(msg, text)
1265
1266 def test_simple_multipart(self):
1267 msg, text = self._msgobj('msg_04.txt')
1268 self._idempotent(msg, text)
1269
1270 def test_MIME_digest(self):
1271 msg, text = self._msgobj('msg_02.txt')
1272 self._idempotent(msg, text)
1273
Barry Warsaw19698172002-06-29 15:23:39 +00001274 def test_long_header(self):
1275 msg, text = self._msgobj('msg_27.txt')
1276 self._idempotent(msg, text)
1277
Barry Warsaw329d3af2002-07-09 02:38:24 +00001278 def test_MIME_digest_with_part_headers(self):
1279 msg, text = self._msgobj('msg_28.txt')
1280 self._idempotent(msg, text)
Barry Warsawb6a92132002-06-28 23:49:33 +00001281
Barry Warsaw41075852001-09-23 03:18:13 +00001282 def test_mixed_with_image(self):
1283 msg, text = self._msgobj('msg_06.txt')
1284 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +00001285
Barry Warsaw41075852001-09-23 03:18:13 +00001286 def test_multipart_report(self):
1287 msg, text = self._msgobj('msg_05.txt')
1288 self._idempotent(msg, text)
Barry Warsaw65279d02001-09-26 05:47:08 +00001289
1290 def test_dsn(self):
1291 msg, text = self._msgobj('msg_16.txt')
1292 self._idempotent(msg, text)
Tim Peters527e64f2001-10-04 05:36:56 +00001293
Barry Warsaw1f0fa922001-10-19 04:08:59 +00001294 def test_preamble_epilogue(self):
1295 msg, text = self._msgobj('msg_21.txt')
1296 self._idempotent(msg, text)
1297
Barry Warsaw763af412002-01-27 06:48:47 +00001298 def test_multipart_one_part(self):
1299 msg, text = self._msgobj('msg_23.txt')
1300 self._idempotent(msg, text)
1301
Barry Warsaw409a4c02002-04-10 21:01:31 +00001302 def test_multipart_no_parts(self):
1303 msg, text = self._msgobj('msg_24.txt')
1304 self._idempotent(msg, text)
1305
Barry Warsaw41075852001-09-23 03:18:13 +00001306 def test_content_type(self):
1307 eq = self.assertEquals
Barry Warsaw2c685062002-06-02 19:09:27 +00001308 unless = self.failUnless
Barry Warsaw41075852001-09-23 03:18:13 +00001309 # Get a message object and reset the seek pointer for other tests
1310 msg, text = self._msgobj('msg_05.txt')
1311 eq(msg.get_type(), 'multipart/report')
1312 # Test the Content-Type: parameters
1313 params = {}
Barry Warsaw65279d02001-09-26 05:47:08 +00001314 for pk, pv in msg.get_params():
Barry Warsaw41075852001-09-23 03:18:13 +00001315 params[pk] = pv
1316 eq(params['report-type'], 'delivery-status')
Barry Warsaw65279d02001-09-26 05:47:08 +00001317 eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
Barry Warsaw41075852001-09-23 03:18:13 +00001318 eq(msg.preamble, 'This is a MIME-encapsulated message.\n\n')
1319 eq(msg.epilogue, '\n\n')
1320 eq(len(msg.get_payload()), 3)
1321 # Make sure the subparts are what we expect
1322 msg1 = msg.get_payload(0)
1323 eq(msg1.get_type(), 'text/plain')
1324 eq(msg1.get_payload(), 'Yadda yadda yadda\n')
1325 msg2 = msg.get_payload(1)
1326 eq(msg2.get_type(), None)
1327 eq(msg2.get_payload(), 'Yadda yadda yadda\n')
1328 msg3 = msg.get_payload(2)
1329 eq(msg3.get_type(), 'message/rfc822')
1330 self.failUnless(isinstance(msg3, Message))
Barry Warsaw2c685062002-06-02 19:09:27 +00001331 payload = msg3.get_payload()
1332 unless(isinstance(payload, ListType))
1333 eq(len(payload), 1)
1334 msg4 = payload[0]
1335 unless(isinstance(msg4, Message))
Barry Warsaw41075852001-09-23 03:18:13 +00001336 eq(msg4.get_payload(), 'Yadda yadda yadda\n')
1337
1338 def test_parser(self):
1339 eq = self.assertEquals
Barry Warsaw2c685062002-06-02 19:09:27 +00001340 unless = self.failUnless
Barry Warsaw41075852001-09-23 03:18:13 +00001341 msg, text = self._msgobj('msg_06.txt')
1342 # Check some of the outer headers
1343 eq(msg.get_type(), 'message/rfc822')
Barry Warsaw2c685062002-06-02 19:09:27 +00001344 # Make sure the payload is a list of exactly one sub-Message, and that
1345 # that submessage has a type of text/plain
1346 payload = msg.get_payload()
1347 unless(isinstance(payload, ListType))
1348 eq(len(payload), 1)
1349 msg1 = payload[0]
Barry Warsaw41075852001-09-23 03:18:13 +00001350 self.failUnless(isinstance(msg1, Message))
1351 eq(msg1.get_type(), 'text/plain')
1352 self.failUnless(isinstance(msg1.get_payload(), StringType))
1353 eq(msg1.get_payload(), '\n')
Barry Warsaw41075852001-09-23 03:18:13 +00001354
Barry Warsaw08a534d2001-10-04 17:58:50 +00001355
1356# Test various other bits of the package's functionality
Barry Warsaw41075852001-09-23 03:18:13 +00001357class TestMiscellaneous(unittest.TestCase):
1358 def test_message_from_string(self):
1359 fp = openfile('msg_01.txt')
1360 try:
1361 text = fp.read()
1362 finally:
1363 fp.close()
1364 msg = email.message_from_string(text)
1365 s = StringIO()
1366 # Don't wrap/continue long headers since we're trying to test
1367 # idempotency.
1368 g = Generator(s, maxheaderlen=0)
Barry Warsaw2c685062002-06-02 19:09:27 +00001369 g.flatten(msg)
Barry Warsaw41075852001-09-23 03:18:13 +00001370 self.assertEqual(text, s.getvalue())
1371
1372 def test_message_from_file(self):
1373 fp = openfile('msg_01.txt')
1374 try:
1375 text = fp.read()
1376 fp.seek(0)
1377 msg = email.message_from_file(fp)
1378 s = StringIO()
1379 # Don't wrap/continue long headers since we're trying to test
1380 # idempotency.
1381 g = Generator(s, maxheaderlen=0)
Barry Warsaw2c685062002-06-02 19:09:27 +00001382 g.flatten(msg)
Barry Warsaw41075852001-09-23 03:18:13 +00001383 self.assertEqual(text, s.getvalue())
1384 finally:
1385 fp.close()
1386
1387 def test_message_from_string_with_class(self):
1388 unless = self.failUnless
1389 fp = openfile('msg_01.txt')
1390 try:
1391 text = fp.read()
1392 finally:
1393 fp.close()
1394 # Create a subclass
1395 class MyMessage(Message):
1396 pass
Tim Peters527e64f2001-10-04 05:36:56 +00001397
Barry Warsaw41075852001-09-23 03:18:13 +00001398 msg = email.message_from_string(text, MyMessage)
1399 unless(isinstance(msg, MyMessage))
1400 # Try something more complicated
1401 fp = openfile('msg_02.txt')
1402 try:
1403 text = fp.read()
1404 finally:
1405 fp.close()
1406 msg = email.message_from_string(text, MyMessage)
1407 for subpart in msg.walk():
1408 unless(isinstance(subpart, MyMessage))
1409
Barry Warsaw41075852001-09-23 03:18:13 +00001410 def test_message_from_file_with_class(self):
1411 unless = self.failUnless
1412 # Create a subclass
1413 class MyMessage(Message):
1414 pass
Tim Peters527e64f2001-10-04 05:36:56 +00001415
Barry Warsaw41075852001-09-23 03:18:13 +00001416 fp = openfile('msg_01.txt')
1417 try:
1418 msg = email.message_from_file(fp, MyMessage)
1419 finally:
1420 fp.close()
1421 unless(isinstance(msg, MyMessage))
1422 # Try something more complicated
1423 fp = openfile('msg_02.txt')
1424 try:
1425 msg = email.message_from_file(fp, MyMessage)
1426 finally:
1427 fp.close()
1428 for subpart in msg.walk():
1429 unless(isinstance(subpart, MyMessage))
1430
Barry Warsawfee435a2001-10-09 19:23:57 +00001431 def test__all__(self):
1432 module = __import__('email')
1433 all = module.__all__
1434 all.sort()
Barry Warsaw16f90552002-04-16 05:06:42 +00001435 self.assertEqual(all, ['Charset', 'Encoders', 'Errors', 'Generator',
1436 'Header', 'Iterators', 'MIMEAudio',
1437 'MIMEBase', 'MIMEImage', 'MIMEMessage',
Barry Warsaw409a4c02002-04-10 21:01:31 +00001438 'MIMEText', 'Message', 'Parser',
Barry Warsaw16f90552002-04-16 05:06:42 +00001439 'Utils', 'base64MIME',
Barry Warsaw409a4c02002-04-10 21:01:31 +00001440 'message_from_file', 'message_from_string',
1441 'quopriMIME'])
Barry Warsawfee435a2001-10-09 19:23:57 +00001442
Barry Warsaw75edc6a2001-11-09 17:46:17 +00001443 def test_formatdate(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001444 now = time.time()
1445 self.assertEqual(Utils.parsedate(Utils.formatdate(now))[:6],
1446 time.gmtime(now)[:6])
Barry Warsaw75edc6a2001-11-09 17:46:17 +00001447
Barry Warsaw4586d2c2001-11-19 18:38:42 +00001448 def test_formatdate_localtime(self):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001449 now = time.time()
1450 self.assertEqual(
1451 Utils.parsedate(Utils.formatdate(now, localtime=1))[:6],
1452 time.localtime(now)[:6])
Barry Warsaw75a40fc2001-11-19 16:31:06 +00001453
1454 def test_parsedate_none(self):
Barry Warsaw19c10ca2001-11-13 18:01:37 +00001455 self.assertEqual(Utils.parsedate(''), None)
1456
Barry Warsaweae36ac2001-12-20 16:37:27 +00001457 def test_parseaddr_empty(self):
1458 self.assertEqual(Utils.parseaddr('<>'), ('', ''))
Barry Warsaw409a4c02002-04-10 21:01:31 +00001459 self.assertEqual(Utils.formataddr(Utils.parseaddr('<>')), '')
1460
1461 def test_noquote_dump(self):
1462 self.assertEqual(
1463 Utils.formataddr(('A Silly Person', 'person@dom.ain')),
1464 'A Silly Person <person@dom.ain>')
1465
1466 def test_escape_dump(self):
1467 self.assertEqual(
1468 Utils.formataddr(('A (Very) Silly Person', 'person@dom.ain')),
1469 r'"A \(Very\) Silly Person" <person@dom.ain>')
1470 a = r'A \(Special\) Person'
1471 b = 'person@dom.ain'
1472 self.assertEqual(Utils.parseaddr(Utils.formataddr((a, b))), (a, b))
1473
1474 def test_quote_dump(self):
1475 self.assertEqual(
1476 Utils.formataddr(('A Silly; Person', 'person@dom.ain')),
1477 r'"A Silly; Person" <person@dom.ain>')
1478
1479 def test_fix_eols(self):
1480 eq = self.assertEqual
1481 eq(Utils.fix_eols('hello'), 'hello')
1482 eq(Utils.fix_eols('hello\n'), 'hello\r\n')
1483 eq(Utils.fix_eols('hello\r'), 'hello\r\n')
1484 eq(Utils.fix_eols('hello\r\n'), 'hello\r\n')
1485 eq(Utils.fix_eols('hello\n\r'), 'hello\r\n\r\n')
1486
1487 def test_charset_richcomparisons(self):
1488 eq = self.assertEqual
1489 ne = self.failIfEqual
1490 cset1 = Charset()
1491 cset2 = Charset()
1492 eq(cset1, 'us-ascii')
1493 eq(cset1, 'US-ASCII')
1494 eq(cset1, 'Us-AsCiI')
1495 eq('us-ascii', cset1)
1496 eq('US-ASCII', cset1)
1497 eq('Us-AsCiI', cset1)
1498 ne(cset1, 'usascii')
1499 ne(cset1, 'USASCII')
1500 ne(cset1, 'UsAsCiI')
1501 ne('usascii', cset1)
1502 ne('USASCII', cset1)
1503 ne('UsAsCiI', cset1)
1504 eq(cset1, cset2)
1505 eq(cset2, cset1)
Barry Warsaweae36ac2001-12-20 16:37:27 +00001506
Barry Warsaw4be9ecc2002-05-22 01:52:10 +00001507 def test_getaddresses(self):
1508 eq = self.assertEqual
1509 eq(Utils.getaddresses(['aperson@dom.ain (Al Person)',
1510 'Bud Person <bperson@dom.ain>']),
1511 [('Al Person', 'aperson@dom.ain'),
1512 ('Bud Person', 'bperson@dom.ain')])
1513
Barry Warsaw41075852001-09-23 03:18:13 +00001514
Barry Warsaw08a534d2001-10-04 17:58:50 +00001515
1516# Test the iterator/generators
Barry Warsaw41075852001-09-23 03:18:13 +00001517class TestIterators(TestEmailBase):
1518 def test_body_line_iterator(self):
1519 eq = self.assertEqual
1520 # First a simple non-multipart message
1521 msg = self._msgobj('msg_01.txt')
1522 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +00001523 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +00001524 eq(len(lines), 6)
1525 eq(EMPTYSTRING.join(lines), msg.get_payload())
1526 # Now a more complicated multipart
1527 msg = self._msgobj('msg_02.txt')
1528 it = Iterators.body_line_iterator(msg)
Barry Warsawd1de6ea2001-10-04 18:18:37 +00001529 lines = list(it)
Barry Warsaw41075852001-09-23 03:18:13 +00001530 eq(len(lines), 43)
Barry Warsaw329d3af2002-07-09 02:38:24 +00001531 fp = openfile('msg_19.txt')
1532 try:
1533 eq(EMPTYSTRING.join(lines), fp.read())
1534 finally:
1535 fp.close()
Barry Warsaw41075852001-09-23 03:18:13 +00001536
1537 def test_typed_subpart_iterator(self):
1538 eq = self.assertEqual
1539 msg = self._msgobj('msg_04.txt')
1540 it = Iterators.typed_subpart_iterator(msg, 'text')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001541 lines = []
1542 subparts = 0
1543 for subpart in it:
1544 subparts += 1
1545 lines.append(subpart.get_payload())
1546 eq(subparts, 2)
Barry Warsaw41075852001-09-23 03:18:13 +00001547 eq(EMPTYSTRING.join(lines), """\
1548a simple kind of mirror
1549to reflect upon our own
1550a simple kind of mirror
1551to reflect upon our own
1552""")
1553
Barry Warsawcdc632c2001-10-15 04:39:02 +00001554 def test_typed_subpart_iterator_default_type(self):
1555 eq = self.assertEqual
1556 msg = self._msgobj('msg_03.txt')
1557 it = Iterators.typed_subpart_iterator(msg, 'text', 'plain')
1558 lines = []
1559 subparts = 0
1560 for subpart in it:
1561 subparts += 1
1562 lines.append(subpart.get_payload())
1563 eq(subparts, 1)
1564 eq(EMPTYSTRING.join(lines), """\
1565
1566Hi,
1567
1568Do you like this message?
1569
1570-Me
1571""")
Barry Warsaw41075852001-09-23 03:18:13 +00001572
Barry Warsaw409a4c02002-04-10 21:01:31 +00001573
Barry Warsaw08a534d2001-10-04 17:58:50 +00001574
Barry Warsaw329d3af2002-07-09 02:38:24 +00001575class TestParsers(TestEmailBase):
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001576 def test_header_parser(self):
1577 eq = self.assertEqual
1578 # Parse only the headers of a complex multipart MIME document
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001579 fp = openfile('msg_02.txt')
Barry Warsaw329d3af2002-07-09 02:38:24 +00001580 try:
1581 msg = HeaderParser().parse(fp)
1582 finally:
1583 fp.close()
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001584 eq(msg['from'], 'ppp-request@zzz.org')
1585 eq(msg['to'], 'ppp@zzz.org')
1586 eq(msg.get_type(), 'multipart/mixed')
1587 eq(msg.is_multipart(), 0)
1588 self.failUnless(isinstance(msg.get_payload(), StringType))
1589
Barry Warsaw409a4c02002-04-10 21:01:31 +00001590 def test_whitespace_continuaton(self):
1591 eq = self.assertEqual
1592 # This message contains a line after the Subject: header that has only
1593 # whitespace, but it is not empty!
1594 msg = email.message_from_string("""\
1595From: aperson@dom.ain
1596To: bperson@dom.ain
1597Subject: the next line has a space on it
Barry Warsaw16f90552002-04-16 05:06:42 +00001598\x20
Barry Warsaw409a4c02002-04-10 21:01:31 +00001599Date: Mon, 8 Apr 2002 15:09:19 -0400
1600Message-ID: spam
1601
1602Here's the message body
1603""")
1604 eq(msg['subject'], 'the next line has a space on it\n ')
1605 eq(msg['message-id'], 'spam')
1606 eq(msg.get_payload(), "Here's the message body\n")
1607
Barry Warsawe0d85c82002-05-19 23:52:54 +00001608 def test_crlf_separation(self):
1609 eq = self.assertEqual
1610 fp = openfile('msg_26.txt')
Barry Warsaw329d3af2002-07-09 02:38:24 +00001611 try:
1612 msg = Parser().parse(fp)
1613 finally:
1614 fp.close()
Barry Warsawe0d85c82002-05-19 23:52:54 +00001615 eq(len(msg.get_payload()), 2)
1616 part1 = msg.get_payload(0)
1617 eq(part1.get_type(), 'text/plain')
1618 eq(part1.get_payload(), 'Simple email with attachment.\r\n\r\n')
1619 part2 = msg.get_payload(1)
1620 eq(part2.get_type(), 'application/riscos')
1621
Barry Warsaw329d3af2002-07-09 02:38:24 +00001622 def test_multipart_digest_with_extra_mime_headers(self):
1623 eq = self.assertEqual
1624 neq = self.ndiffAssertEqual
1625 fp = openfile('msg_28.txt')
1626 try:
1627 msg = email.message_from_file(fp)
1628 finally:
1629 fp.close()
1630 # Structure is:
1631 # multipart/digest
1632 # message/rfc822
1633 # text/plain
1634 # message/rfc822
1635 # text/plain
1636 eq(msg.is_multipart(), 1)
1637 eq(len(msg.get_payload()), 2)
1638 part1 = msg.get_payload(0)
1639 eq(part1.get_type(), 'message/rfc822')
1640 eq(part1.is_multipart(), 1)
1641 eq(len(part1.get_payload()), 1)
1642 part1a = part1.get_payload(0)
1643 eq(part1a.is_multipart(), 0)
1644 eq(part1a.get_type(), 'text/plain')
1645 neq(part1a.get_payload(), 'message 1\n')
1646 # next message/rfc822
1647 part2 = msg.get_payload(1)
1648 eq(part2.get_type(), 'message/rfc822')
1649 eq(part2.is_multipart(), 1)
1650 eq(len(part2.get_payload()), 1)
1651 part2a = part2.get_payload(0)
1652 eq(part2a.is_multipart(), 0)
1653 eq(part2a.get_type(), 'text/plain')
1654 neq(part2a.get_payload(), 'message 2\n')
Barry Warsawb6a92132002-06-28 23:49:33 +00001655
Barry Warsaw409a4c02002-04-10 21:01:31 +00001656
1657
1658class TestBase64(unittest.TestCase):
1659 def test_len(self):
1660 eq = self.assertEqual
1661 eq(base64MIME.base64_len('hello'),
1662 len(base64MIME.encode('hello', eol='')))
1663 for size in range(15):
1664 if size == 0 : bsize = 0
1665 elif size <= 3 : bsize = 4
1666 elif size <= 6 : bsize = 8
1667 elif size <= 9 : bsize = 12
1668 elif size <= 12: bsize = 16
1669 else : bsize = 20
1670 eq(base64MIME.base64_len('x'*size), bsize)
1671
1672 def test_decode(self):
1673 eq = self.assertEqual
1674 eq(base64MIME.decode(''), '')
1675 eq(base64MIME.decode('aGVsbG8='), 'hello')
1676 eq(base64MIME.decode('aGVsbG8=', 'X'), 'hello')
1677 eq(base64MIME.decode('aGVsbG8NCndvcmxk\n', 'X'), 'helloXworld')
1678
1679 def test_encode(self):
1680 eq = self.assertEqual
1681 eq(base64MIME.encode(''), '')
1682 eq(base64MIME.encode('hello'), 'aGVsbG8=\n')
1683 # Test the binary flag
1684 eq(base64MIME.encode('hello\n'), 'aGVsbG8K\n')
1685 eq(base64MIME.encode('hello\n', 0), 'aGVsbG8NCg==\n')
1686 # Test the maxlinelen arg
1687 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40), """\
1688eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1689eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1690eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg
1691eHh4eCB4eHh4IA==
1692""")
1693 # Test the eol argument
1694 eq(base64MIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1695eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1696eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1697eHh4eCB4eHh4IHh4eHggeHh4eCB4eHh4IHh4eHgg\r
1698eHh4eCB4eHh4IA==\r
1699""")
Barry Warsaw16f90552002-04-16 05:06:42 +00001700
Barry Warsaw409a4c02002-04-10 21:01:31 +00001701 def test_header_encode(self):
1702 eq = self.assertEqual
1703 he = base64MIME.header_encode
1704 eq(he('hello'), '=?iso-8859-1?b?aGVsbG8=?=')
1705 eq(he('hello\nworld'), '=?iso-8859-1?b?aGVsbG8NCndvcmxk?=')
1706 # Test the charset option
1707 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?b?aGVsbG8=?=')
1708 # Test the keep_eols flag
1709 eq(he('hello\nworld', keep_eols=1),
1710 '=?iso-8859-1?b?aGVsbG8Kd29ybGQ=?=')
1711 # Test the maxlinelen argument
1712 eq(he('xxxx ' * 20, maxlinelen=40), """\
1713=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=
1714 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=
1715 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=
1716 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=
1717 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=
1718 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1719 # Test the eol argument
1720 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1721=?iso-8859-1?b?eHh4eCB4eHh4IHh4eHggeHg=?=\r
1722 =?iso-8859-1?b?eHggeHh4eCB4eHh4IHh4eHg=?=\r
1723 =?iso-8859-1?b?IHh4eHggeHh4eCB4eHh4IHg=?=\r
1724 =?iso-8859-1?b?eHh4IHh4eHggeHh4eCB4eHg=?=\r
1725 =?iso-8859-1?b?eCB4eHh4IHh4eHggeHh4eCA=?=\r
1726 =?iso-8859-1?b?eHh4eCB4eHh4IHh4eHgg?=""")
1727
1728
1729
1730class TestQuopri(unittest.TestCase):
1731 def setUp(self):
1732 self.hlit = [chr(x) for x in range(ord('a'), ord('z')+1)] + \
1733 [chr(x) for x in range(ord('A'), ord('Z')+1)] + \
1734 [chr(x) for x in range(ord('0'), ord('9')+1)] + \
1735 ['!', '*', '+', '-', '/', ' ']
1736 self.hnon = [chr(x) for x in range(256) if chr(x) not in self.hlit]
1737 assert len(self.hlit) + len(self.hnon) == 256
1738 self.blit = [chr(x) for x in range(ord(' '), ord('~')+1)] + ['\t']
1739 self.blit.remove('=')
1740 self.bnon = [chr(x) for x in range(256) if chr(x) not in self.blit]
1741 assert len(self.blit) + len(self.bnon) == 256
1742
1743 def test_header_quopri_check(self):
1744 for c in self.hlit:
1745 self.failIf(quopriMIME.header_quopri_check(c))
1746 for c in self.hnon:
1747 self.failUnless(quopriMIME.header_quopri_check(c))
1748
1749 def test_body_quopri_check(self):
1750 for c in self.blit:
1751 self.failIf(quopriMIME.body_quopri_check(c))
1752 for c in self.bnon:
1753 self.failUnless(quopriMIME.body_quopri_check(c))
1754
1755 def test_header_quopri_len(self):
1756 eq = self.assertEqual
1757 hql = quopriMIME.header_quopri_len
1758 enc = quopriMIME.header_encode
1759 for s in ('hello', 'h@e@l@l@o@'):
1760 # Empty charset and no line-endings. 7 == RFC chrome
1761 eq(hql(s), len(enc(s, charset='', eol=''))-7)
1762 for c in self.hlit:
1763 eq(hql(c), 1)
1764 for c in self.hnon:
1765 eq(hql(c), 3)
1766
1767 def test_body_quopri_len(self):
1768 eq = self.assertEqual
1769 bql = quopriMIME.body_quopri_len
1770 for c in self.blit:
1771 eq(bql(c), 1)
1772 for c in self.bnon:
1773 eq(bql(c), 3)
1774
1775 def test_quote_unquote_idempotent(self):
1776 for x in range(256):
1777 c = chr(x)
1778 self.assertEqual(quopriMIME.unquote(quopriMIME.quote(c)), c)
1779
1780 def test_header_encode(self):
1781 eq = self.assertEqual
1782 he = quopriMIME.header_encode
1783 eq(he('hello'), '=?iso-8859-1?q?hello?=')
1784 eq(he('hello\nworld'), '=?iso-8859-1?q?hello=0D=0Aworld?=')
1785 # Test the charset option
1786 eq(he('hello', charset='iso-8859-2'), '=?iso-8859-2?q?hello?=')
1787 # Test the keep_eols flag
1788 eq(he('hello\nworld', keep_eols=1), '=?iso-8859-1?q?hello=0Aworld?=')
1789 # Test a non-ASCII character
Jack Jansen1476c272002-04-23 10:52:44 +00001790 eq(he('hello\xc7there'), '=?iso-8859-1?q?hello=C7there?=')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001791 # Test the maxlinelen argument
1792 eq(he('xxxx ' * 20, maxlinelen=40), """\
1793=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=
1794 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=
1795 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=
1796 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=
1797 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
1798 # Test the eol argument
1799 eq(he('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1800=?iso-8859-1?q?xxxx_xxxx_xxxx_xxxx_xx?=\r
1801 =?iso-8859-1?q?xx_xxxx_xxxx_xxxx_xxxx?=\r
1802 =?iso-8859-1?q?_xxxx_xxxx_xxxx_xxxx_x?=\r
1803 =?iso-8859-1?q?xxx_xxxx_xxxx_xxxx_xxx?=\r
1804 =?iso-8859-1?q?x_xxxx_xxxx_?=""")
1805
1806 def test_decode(self):
1807 eq = self.assertEqual
1808 eq(quopriMIME.decode(''), '')
1809 eq(quopriMIME.decode('hello'), 'hello')
1810 eq(quopriMIME.decode('hello', 'X'), 'hello')
1811 eq(quopriMIME.decode('hello\nworld', 'X'), 'helloXworld')
1812
1813 def test_encode(self):
1814 eq = self.assertEqual
1815 eq(quopriMIME.encode(''), '')
1816 eq(quopriMIME.encode('hello'), 'hello')
1817 # Test the binary flag
1818 eq(quopriMIME.encode('hello\r\nworld'), 'hello\nworld')
1819 eq(quopriMIME.encode('hello\r\nworld', 0), 'hello\nworld')
1820 # Test the maxlinelen arg
1821 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40), """\
1822xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=
1823 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=
1824x xxxx xxxx xxxx xxxx=20""")
1825 # Test the eol argument
1826 eq(quopriMIME.encode('xxxx ' * 20, maxlinelen=40, eol='\r\n'), """\
1827xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx=\r
1828 xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxx=\r
1829x xxxx xxxx xxxx xxxx=20""")
1830 eq(quopriMIME.encode("""\
1831one line
1832
1833two line"""), """\
1834one line
1835
1836two line""")
Barry Warsaw16f90552002-04-16 05:06:42 +00001837
Barry Warsaw409a4c02002-04-10 21:01:31 +00001838
1839
1840# Test the Charset class
1841class TestCharset(unittest.TestCase):
1842 def test_idempotent(self):
1843 eq = self.assertEqual
1844 # Make sure us-ascii = no Unicode conversion
1845 c = Charset('us-ascii')
1846 s = 'Hello World!'
1847 sp = c.to_splittable(s)
1848 eq(s, c.from_splittable(sp))
1849 # test 8-bit idempotency with us-ascii
1850 s = '\xa4\xa2\xa4\xa4\xa4\xa6\xa4\xa8\xa4\xaa'
1851 sp = c.to_splittable(s)
1852 eq(s, c.from_splittable(sp))
1853
1854
1855
1856# Test multilingual MIME headers.
Barry Warsawb6a92132002-06-28 23:49:33 +00001857class TestHeader(TestEmailBase):
Barry Warsaw409a4c02002-04-10 21:01:31 +00001858 def test_simple(self):
Barry Warsawb6a92132002-06-28 23:49:33 +00001859 eq = self.ndiffAssertEqual
1860 h = Header('Hello World!')
1861 eq(h.encode(), 'Hello World!')
1862 h.append(' Goodbye World!')
1863 eq(h.encode(), 'Hello World! Goodbye World!')
1864
1865 def test_simple_surprise(self):
1866 eq = self.ndiffAssertEqual
Barry Warsaw409a4c02002-04-10 21:01:31 +00001867 h = Header('Hello World!')
1868 eq(h.encode(), 'Hello World!')
1869 h.append('Goodbye World!')
Barry Warsawb6a92132002-06-28 23:49:33 +00001870 eq(h.encode(), 'Hello World!Goodbye World!')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001871
1872 def test_header_needs_no_decoding(self):
1873 h = 'no decoding needed'
1874 self.assertEqual(decode_header(h), [(h, None)])
1875
1876 def test_long(self):
1877 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.",
1878 maxlinelen=76)
1879 for l in h.encode().split('\n '):
1880 self.failUnless(len(l) <= 76)
1881
1882 def test_multilingual(self):
Barry Warsawc53b29e2002-07-09 16:36:36 +00001883 eq = self.ndiffAssertEqual
Barry Warsaw409a4c02002-04-10 21:01:31 +00001884 g = Charset("iso-8859-1")
1885 cz = Charset("iso-8859-2")
1886 utf8 = Charset("utf-8")
1887 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. "
1888 cz_head = "Finan\xe8ni metropole se hroutily pod tlakem jejich d\xf9vtipu.. "
1889 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")
1890 h = Header(g_head, g)
1891 h.append(cz_head, cz)
1892 h.append(utf8_head, utf8)
1893 enc = h.encode()
1894 eq(enc, """=?iso-8859-1?q?Die_Mieter_treten_hier_ein_werden_mit_eine?=
1895 =?iso-8859-1?q?m_Foerderband_komfortabel_den_Korridor_ent?=
1896 =?iso-8859-1?q?lang=2C_an_s=FCdl=FCndischen_Wandgem=E4lden_vorbei?=
1897 =?iso-8859-1?q?=2C_gegen_die_rotierenden_Klingen_bef=F6rdert=2E_?=
1898 =?iso-8859-2?q?Finan=E8ni_metropole_se_hroutil?=
1899 =?iso-8859-2?q?y_pod_tlakem_jejich_d=F9vtipu=2E=2E_?=
1900 =?utf-8?b?5q2j56K644Gr6KiA44GG44Go57+76Kiz44Gv?=
1901 =?utf-8?b?44GV44KM44Gm44GE44G+44Gb44KT44CC5LiA?=
1902 =?utf-8?b?6YOo44Gv44OJ44Kk44OE6Kqe44Gn44GZ44GM?=
1903 =?utf-8?b?44CB44GC44Go44Gv44Gn44Gf44KJ44KB44Gn?=
1904 =?utf-8?b?44GZ44CC5a6f6Zqb44Gr44Gv44CMV2VubiBpc3QgZGE=?=
1905 =?utf-8?b?cyBOdW5zdHVjayBnaXQgdW5k?=
1906 =?utf-8?b?IFNsb3Rlcm1leWVyPyBKYSEgQmVpaGVyaHVuZCBkYXMgT2Rl?=
1907 =?utf-8?b?ciBkaWUgRmxpcHBlcndhbGR0?=
1908 =?utf-8?b?IGdlcnNwdXQu44CN44Go6KiA44Gj44Gm44GE44G+44GZ44CC?=""")
1909 eq(decode_header(enc),
1910 [(g_head, "iso-8859-1"), (cz_head, "iso-8859-2"),
1911 (utf8_head, "utf-8")])
Barry Warsaw3fdc8892002-06-29 03:27:27 +00001912 # Test for conversion to unicode. BAW: Python 2.1 doesn't support the
1913 # __unicode__() protocol, so do things this way for compatibility.
1914 ustr = h.__unicode__()
1915 # For Python 2.2 and beyond
1916 #ustr = unicode(h)
1917 eq(ustr.encode('utf-8'),
1918 'Die Mieter treten hier ein werden mit einem Foerderband '
1919 'komfortabel den Korridor entlang, an s\xc3\xbcdl\xc3\xbcndischen '
1920 'Wandgem\xc3\xa4lden vorbei, gegen die rotierenden Klingen '
1921 'bef\xc3\xb6rdert. Finan\xc4\x8dni metropole se hroutily pod '
1922 'tlakem jejich d\xc5\xafvtipu.. \xe6\xad\xa3\xe7\xa2\xba\xe3\x81'
1923 '\xab\xe8\xa8\x80\xe3\x81\x86\xe3\x81\xa8\xe7\xbf\xbb\xe8\xa8\xb3'
1924 '\xe3\x81\xaf\xe3\x81\x95\xe3\x82\x8c\xe3\x81\xa6\xe3\x81\x84\xe3'
1925 '\x81\xbe\xe3\x81\x9b\xe3\x82\x93\xe3\x80\x82\xe4\xb8\x80\xe9\x83'
1926 '\xa8\xe3\x81\xaf\xe3\x83\x89\xe3\x82\xa4\xe3\x83\x84\xe8\xaa\x9e'
1927 '\xe3\x81\xa7\xe3\x81\x99\xe3\x81\x8c\xe3\x80\x81\xe3\x81\x82\xe3'
1928 '\x81\xa8\xe3\x81\xaf\xe3\x81\xa7\xe3\x81\x9f\xe3\x82\x89\xe3\x82'
1929 '\x81\xe3\x81\xa7\xe3\x81\x99\xe3\x80\x82\xe5\xae\x9f\xe9\x9a\x9b'
1930 '\xe3\x81\xab\xe3\x81\xaf\xe3\x80\x8cWenn ist das Nunstuck git '
1931 'und Slotermeyer? Ja! Beiherhund das Oder die Flipperwaldt '
1932 'gersput.\xe3\x80\x8d\xe3\x81\xa8\xe8\xa8\x80\xe3\x81\xa3\xe3\x81'
1933 '\xa6\xe3\x81\x84\xe3\x81\xbe\xe3\x81\x99\xe3\x80\x82')
Barry Warsawc53b29e2002-07-09 16:36:36 +00001934 # Test make_header()
1935 newh = make_header(decode_header(enc))
1936 eq(newh, enc)
1937
1938 def test_header_ctor_default_args(self):
1939 eq = self.ndiffAssertEqual
1940 h = Header()
1941 eq(h, '')
1942 h.append('foo', Charset('iso-8859-1'))
1943 eq(h, '=?iso-8859-1?q?foo?=')
Barry Warsaw409a4c02002-04-10 21:01:31 +00001944
Barry Warsawe0d85c82002-05-19 23:52:54 +00001945 def test_explicit_maxlinelen(self):
Barry Warsawb6a92132002-06-28 23:49:33 +00001946 eq = self.ndiffAssertEqual
Barry Warsawe0d85c82002-05-19 23:52:54 +00001947 hstr = 'A very long line that must get split to something other than at the 76th character boundary to test the non-default behavior'
1948 h = Header(hstr)
1949 eq(h.encode(), '''\
Barry Warsawb6a92132002-06-28 23:49:33 +00001950A very long line that must get split to something other than at the 76th
1951 character boundary to test the non-default behavior''')
Barry Warsawe0d85c82002-05-19 23:52:54 +00001952 h = Header(hstr, header_name='Subject')
1953 eq(h.encode(), '''\
1954A very long line that must get split to something other than at the
Barry Warsawb6a92132002-06-28 23:49:33 +00001955 76th character boundary to test the non-default behavior''')
Barry Warsawe0d85c82002-05-19 23:52:54 +00001956 h = Header(hstr, maxlinelen=1024, header_name='Subject')
1957 eq(h.encode(), hstr)
1958
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001959
Barry Warsaw9546e792002-06-29 05:58:45 +00001960# Test RFC 2231 header parameters decoding
1961class TestRFC2231(TestEmailBase):
1962 def test_get_param(self):
1963 eq = self.assertEqual
1964 msg = self._msgobj('msg_29.txt')
1965 eq(msg.get_param('title'),
1966 ('us-ascii', 'en', 'This is even more ***fun*** isn\'t it!'))
1967 eq(msg.get_param('title', unquote=0),
1968 ('us-ascii', 'en', '"This is even more ***fun*** isn\'t it!"'))
1969
1970
Barry Warsawbf7a59d2001-10-11 15:44:50 +00001971
Barry Warsawc9ad32c2002-04-15 22:14:06 +00001972def _testclasses():
1973 mod = sys.modules[__name__]
1974 return [getattr(mod, name) for name in dir(mod) if name.startswith('Test')]
1975
1976
Barry Warsaw41075852001-09-23 03:18:13 +00001977def suite():
1978 suite = unittest.TestSuite()
Barry Warsawc9ad32c2002-04-15 22:14:06 +00001979 for testclass in _testclasses():
1980 suite.addTest(unittest.makeSuite(testclass))
Barry Warsaw41075852001-09-23 03:18:13 +00001981 return suite
1982
1983
Barry Warsawc9ad32c2002-04-15 22:14:06 +00001984def test_main():
1985 for testclass in _testclasses():
1986 test_support.run_unittest(testclass)
1987
1988
Barry Warsaw08a534d2001-10-04 17:58:50 +00001989
Guido van Rossum78f0dd32001-12-07 21:07:08 +00001990if __name__ == '__main__':
Barry Warsaw409a4c02002-04-10 21:01:31 +00001991 unittest.main(defaultTest='suite')