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